/** Bitable API client — thin wrapper over /api/v1/bitable endpoints. */

import { BaseApiClient } from './base'

// ── Domain types (co-located with API client) ──────────────────────────

export type FieldType =
  | 'text'
  | 'number'
  | 'date'
  | 'select'
  | 'multiselect'
  | 'attachment'
  | 'image'
  | 'formula'
  | 'lookup'

export type FieldOwner = 'agent' | 'user'

export type ViewType = 'grid' | 'kanban' | 'gantt' | 'gallery' | 'form'

export type RecalcStatus = 'pending' | 'calculating' | 'done' | 'error'

/** Top-level container grouping related tables (R1, 多维表格文件). */
export interface IBitableFile {
  id: string
  name: string
  icon: string
  description: string
  owner_user_id: string | null
  created_at: string
  updated_at: string
}

export interface IBitableTable {
  id: string
  file_id: string | null
  name: string
  description: string
  primary_key_field_id: string | null
  owner_user_id: string | null
  created_at: string
  updated_at: string
}

export interface IBitableField {
  id: string
  table_id: string
  name: string
  field_type: FieldType
  config: Record<string, unknown>
  owner: FieldOwner
  created_at: string
}

export interface IBitableRecord {
  id: string
  table_id: string
  values: Record<string, unknown>
  created_at: string
  updated_at: string
}

export interface IBitableView {
  id: string
  table_id: string
  name: string
  view_type: ViewType
  config: Record<string, unknown>
  created_at: string
}

/** File metadata stored in attachment/image field values (JSONB array). */
export interface IAttachmentMeta {
  filename: string
  stored_name: string
  mime_type: string
  size: number
  url: string
}

// ── Request types ──────────────────────────────────────────────────────

export interface ICreateFileRequest {
  name: string
  icon?: string
  description?: string
}

export interface IUpdateFileRequest {
  name?: string
  icon?: string
  description?: string
}

export interface ICreateTableRequest {
  name: string
  description?: string
  primary_key_field_id?: string | null
}

export interface ICreateFieldRequest {
  name: string
  field_type: FieldType
  config?: Record<string, unknown>
  owner?: FieldOwner
}

export interface ICreateRecordRequest {
  values: Record<string, unknown>
}

export interface IUpdateRecordRequest {
  values: Record<string, unknown>
}

export interface IUpsertRequest {
  records: Record<string, unknown>[]
  primary_key_field_id: string
}

export interface ICreateViewRequest {
  name: string
  view_type?: ViewType
  config?: Record<string, unknown>
}

// ── Response types ─────────────────────────────────────────────────────

export interface IListRecordsResponse {
  success: boolean
  records: IBitableRecord[]
  next_cursor: string | null
}

// ── Runtime type guard ─────────────────────────────────────────────────

export function isBitableTable(value: unknown): value is IBitableTable {
  if (typeof value !== 'object' || value === null) return false
  const v = value as Record<string, unknown>
  return typeof v.id === 'string' && typeof v.name === 'string'
}

// ── API client ─────────────────────────────────────────────────────────

const API_BASE = '/api/v1/bitable'

class BitableApiClient extends BaseApiClient {
  constructor(baseUrl: string = API_BASE) {
    super(baseUrl)
  }

  // ── Files (R1) ───────────────────────────────────────

  async listFiles(): Promise<{ success: boolean; files: IBitableFile[] }> {
    return this.request('/files', { method: 'GET' })
  }

  async createFile(
    data: ICreateFileRequest,
  ): Promise<{ success: boolean; file: IBitableFile }> {
    return this.request('/files', {
      method: 'POST',
      body: JSON.stringify(data),
    })
  }

  async getFile(
    fileId: string,
  ): Promise<{ success: boolean; file: IBitableFile }> {
    return this.request(`/files/${fileId}`, { method: 'GET' })
  }

  async updateFile(
    fileId: string,
    data: IUpdateFileRequest,
  ): Promise<{ success: boolean; file: IBitableFile }> {
    return this.request(`/files/${fileId}`, {
      method: 'PATCH',
      body: JSON.stringify(data),
    })
  }

  async deleteFile(fileId: string): Promise<{ success: boolean }> {
    return this.request(`/files/${fileId}`, { method: 'DELETE' })
  }

  async listTablesInFile(
    fileId: string,
  ): Promise<{ success: boolean; tables: IBitableTable[] }> {
    return this.request(`/files/${fileId}/tables`, { method: 'GET' })
  }

  async createTableInFile(
    fileId: string,
    data: ICreateTableRequest,
  ): Promise<{ success: boolean; table: IBitableTable }> {
    return this.request(`/files/${fileId}/tables`, {
      method: 'POST',
      body: JSON.stringify(data),
    })
  }

  // ── Tables (deprecated — prefer file-scoped endpoints above) ───────

  async listTables(): Promise<{ success: boolean; tables: IBitableTable[] }> {
    return this.request('/tables', { method: 'GET' })
  }

  async createTable(
    data: ICreateTableRequest,
  ): Promise<{ success: boolean; table: IBitableTable }> {
    return this.request('/tables', {
      method: 'POST',
      body: JSON.stringify(data),
    })
  }

  async getTable(
    tableId: string,
  ): Promise<{ success: boolean; table: IBitableTable }> {
    return this.request(`/tables/${tableId}`, { method: 'GET' })
  }

  async updateTable(
    tableId: string,
    data: Partial<ICreateTableRequest>,
  ): Promise<{ success: boolean; table: IBitableTable }> {
    return this.request(`/tables/${tableId}`, {
      method: 'PATCH',
      body: JSON.stringify(data),
    })
  }

  async deleteTable(tableId: string): Promise<{ success: boolean }> {
    return this.request(`/tables/${tableId}`, { method: 'DELETE' })
  }

  // ── Fields ───────────────────────────────────────────

  async listFields(
    tableId: string,
  ): Promise<{ success: boolean; fields: IBitableField[] }> {
    return this.request(`/tables/${tableId}/fields`, { method: 'GET' })
  }

  async createField(
    tableId: string,
    data: ICreateFieldRequest,
  ): Promise<{ success: boolean; field: IBitableField }> {
    return this.request(`/tables/${tableId}/fields`, {
      method: 'POST',
      body: JSON.stringify(data),
    })
  }

  async updateField(
    fieldId: string,
    data: Partial<ICreateFieldRequest>,
  ): Promise<{ success: boolean; field: IBitableField }> {
    return this.request(`/fields/${fieldId}`, {
      method: 'PATCH',
      body: JSON.stringify(data),
    })
  }

  async deleteField(
    fieldId: string,
    force = false,
  ): Promise<{ success: boolean }> {
    const qs = force ? '?force=true' : ''
    return this.request(`/fields/${fieldId}${qs}`, { method: 'DELETE' })
  }

  // ── Formula validation (U5b) ─────────────────────────

  async validateFormula(
    formula: string,
  ): Promise<{ valid: boolean; error?: string }> {
    return this.request('/fields/validate-formula', {
      method: 'POST',
      body: JSON.stringify({ formula }),
    })
  }

  // ── Records ──────────────────────────────────────────

  async listRecords(
    tableId: string,
    params?: {
      cursor?: string
      limit?: number
      filters?: string
      sorts?: string
    },
  ): Promise<IListRecordsResponse> {
    const sp = new URLSearchParams()
    if (params?.cursor) sp.set('cursor', params.cursor)
    if (params?.limit) sp.set('limit', String(params.limit))
    if (params?.filters) sp.set('filters', params.filters)
    if (params?.sorts) sp.set('sorts', params.sorts)
    const qs = sp.toString()
    const path = qs ? `/tables/${tableId}/records?${qs}` : `/tables/${tableId}/records`
    return this.request(path, { method: 'GET' })
  }

  async createRecords(
    tableId: string,
    records: Record<string, unknown>[],
  ): Promise<{ success: boolean; count: number; records: IBitableRecord[] }> {
    // P2 #20: client-side batch limit matches backend max_length=500.
    const BATCH_LIMIT = 500
    if (records.length <= BATCH_LIMIT) {
      return this.request(`/tables/${tableId}/records`, {
        method: 'POST',
        body: JSON.stringify({ records }),
      })
    }
    // Chunk large batches to avoid 422 from backend validation.
    const allRecords: IBitableRecord[] = []
    for (let i = 0; i < records.length; i += BATCH_LIMIT) {
      const chunk = records.slice(i, i + BATCH_LIMIT)
      const resp = await this.request<{ success: boolean; count: number; records: IBitableRecord[] }>(
        `/tables/${tableId}/records`,
        { method: 'POST', body: JSON.stringify({ records: chunk }) },
      )
      if (resp.records) allRecords.push(...resp.records)
    }
    return { success: true, count: allRecords.length, records: allRecords }
  }

  async updateRecord(
    recordId: string,
    values: Record<string, unknown>,
  ): Promise<{ success: boolean; record: IBitableRecord }> {
    return this.request(`/records/${recordId}`, {
      method: 'PATCH',
      body: JSON.stringify({ values }),
    })
  }

  async deleteRecord(recordId: string): Promise<{ success: boolean }> {
    return this.request(`/records/${recordId}`, { method: 'DELETE' })
  }

  // ── Upsert (KTD8) ────────────────────────────────────

  async upsertRecords(
    tableId: string,
    data: IUpsertRequest,
  ): Promise<{ success: boolean; upserted: number; inserted: number; updated: number }> {
    return this.request(`/tables/${tableId}/upsert`, {
      method: 'POST',
      body: JSON.stringify(data),
    })
  }

  // ── Views ────────────────────────────────────────────

  async listViews(
    tableId: string,
  ): Promise<{ success: boolean; views: IBitableView[] }> {
    return this.request(`/tables/${tableId}/views`, { method: 'GET' })
  }

  async createView(
    tableId: string,
    data: ICreateViewRequest,
  ): Promise<{ success: boolean; view: IBitableView }> {
    return this.request(`/tables/${tableId}/views`, {
      method: 'POST',
      body: JSON.stringify(data),
    })
  }

  async updateView(
    viewId: string,
    data: Partial<ICreateViewRequest>,
  ): Promise<{ success: boolean; view: IBitableView }> {
    return this.request(`/views/${viewId}`, {
      method: 'PATCH',
      body: JSON.stringify(data),
    })
  }

  /**
   * Delete a view (U6: R15a). Returns 204 No Content on success.
   * The last view of a table cannot be deleted (backend returns 409).
   */
  async deleteView(viewId: string): Promise<void> {
    await this.request(`/views/${viewId}`, { method: 'DELETE' })
  }

  // ── File upload (U6: attachment & image) ──────────────

  async uploadFile(
    tableId: string,
    fieldId: string,
    file: File,
  ): Promise<IAttachmentMeta> {
    const formData = new FormData()
    formData.append('file', file)
    return this.request(
      `/tables/${tableId}/upload?field_id=${encodeURIComponent(fieldId)}`,
      { method: 'POST', body: formData },
    )
  }
}

export const bitableApi = new BitableApiClient()
