import { ApiClient, type ApiClientOptions } from "./client"
import type { ApiPathByMethod, ApiQueryParams, ApiRequestBody, ApiResponse } from "./types"
import type { ApiListQueryParams } from "../contracts/types"

export const DEFAULT_PER_PAGE = 10

type CrudIndexRoute = ApiPathByMethod<"get"> & ApiPathByMethod<"post">
type CrudByIdRoute = ApiPathByMethod<"put"> & ApiPathByMethod<"delete">
type CrudImportRoute = ApiPathByMethod<"post">
type CrudExportRoute = ApiPathByMethod<"get"> | ApiPathByMethod<"post">

export type CrudTypeOverrides = {
    listResponse?: unknown
    showResponse?: unknown
    createPayload?: unknown
    createResponse?: unknown
    updatePayload?: unknown
    updateResponse?: unknown
    destroyResponse?: unknown
}

type Resolve<T, K extends string, Default> = T extends Record<K, infer V> ? V : Default

export abstract class CrudClient<
    IndexRoute extends CrudIndexRoute,
    ByIdRoute extends CrudByIdRoute,
    Overrides extends CrudTypeOverrides = {},
> extends ApiClient {


    constructor(baseUrl?: string, defaultOptions?: ApiClientOptions,

        public indexRoute?: IndexRoute,
        public byIdRoute?: ByIdRoute,
        public importRoute?: CrudImportRoute,
        public exportRoute?: CrudExportRoute,
        public exportMethod: "GET" | "POST" = "GET") {

        super(baseUrl, defaultOptions)

    }

    async list(query?: ApiListQueryParams): Promise<Resolve<Overrides, 'listResponse', ApiResponse<IndexRoute, "get">>> {
        return this.get(this.indexRoute as IndexRoute, query ? { query } as never : undefined) as never
    }

    async show(id: string): Promise<Resolve<Overrides, 'showResponse', ApiResponse<ByIdRoute & ApiPathByMethod<"get">, "get">>> {
        return this.get(this.byIdRoute as ByIdRoute & ApiPathByMethod<"get">, { params: { id } } as never) as never
    }

    async create(payload: Resolve<Overrides, 'createPayload', ApiRequestBody<IndexRoute, "post">>): Promise<Resolve<Overrides, 'createResponse', ApiResponse<IndexRoute, "post">>> {
        return this.post<IndexRoute>(this.indexRoute as IndexRoute, payload as never) as never
    }

    async update(id: string, payload: Resolve<Overrides, 'updatePayload', ApiRequestBody<ByIdRoute, "put">>): Promise<Resolve<Overrides, 'updateResponse', ApiResponse<ByIdRoute, "put">>> {
        return this.put<ByIdRoute>(this.byIdRoute as ByIdRoute, payload as never, { params: { id } } as never) as never
    }

    async destroy(id: string): Promise<Resolve<Overrides, 'destroyResponse', ApiResponse<ByIdRoute, "delete">>> {
        return this.delete<ByIdRoute>(this.byIdRoute as ByIdRoute, { params: { id } } as never) as never
    }

    async importData(file: File): Promise<ImportDataResponse> {
        const formData = new FormData()
        formData.append("file", file)
        const route = (this.importRoute ?? `${this.indexRoute}/import`) as string
        return this.postFormData(route, formData)
    }

    async exportData(filters?: Record<string, unknown>): Promise<Blob> {
        const route = (this.exportRoute ?? `${this.indexRoute}/export`) as string
        if (this.exportMethod === "POST") {
            return this.fetchBlob(route, { method: "POST", body: filters ?? {} })
        }
        return this.fetchBlob(route, { method: "GET" })
    }
}

export type BaseCrudItem = { id: number }

export type ImportFailureRow = {
    row: number
    errors: {
        field: string
        label: string
        value: unknown
        message: string
        valid_examples?: string[]
    }[]
}

export type ImportDataResponse = {
    message?: string
    data?: {
        imported_count: number
        failed_count: number
        failed_rows: ImportFailureRow[]
    }
}

type ClientMethodReturn<C, TMethod extends PropertyKey> =
    C extends { [K in TMethod]: (...args: any[]) => infer TResult }
        ? Awaited<TResult>
        : never

type ClientMethodFirstArg<C, TMethod extends PropertyKey> =
    C extends { [K in TMethod]: (arg: infer TArg, ...args: any[]) => any }
        ? Exclude<TArg, undefined>
        : never

/** Extract the list response type from a client with a `list()` method. */
export type CrudListResponse<C> = ClientMethodReturn<C, "list">

/** Extract the show response type from a client with a `show()` method. */
export type CrudShowResponse<C> = ClientMethodReturn<C, "show">

/** Extract a single item type from the `data` array of a CrudClient list response. */
export type CrudListItem<C> = CrudListResponse<C> extends { data?: readonly (infer Item)[] } ? Item : never

/** Extract the query-parameter type accepted by a client's `list()` method. */
export type CrudListParams<C> = ClientMethodFirstArg<C, "list">
