import type { ZElement } from '../core/types';
import type { ComponentData, ComponentKind, ComponentProp, ComponentSummary } from '../core/components';
import { ApiError, isSessionExpired, refreshNonce, SESSION_EXPIRED_MESSAGE, type RestConfig } from './client';

/**
 * REST client of the components / global widgets endpoints (T168 / T210, `Zadex\Components\ComponentsController`):
 *
 *  GET    /components            summaries (id, title, kind, usage, modified)
 *  POST   /components            { title, elements, css, props?, kind? } -> ComponentData (201)
 *  GET    /components/{id}       ComponentData
 *  PUT    /components/{id}       { elements?, css?, props?, title? } -> ComponentData
 *  DELETE /components/{id}       { deleted, id, usage }
 */
export interface CreateComponentBody {
  title: string;
  elements: ZElement[];
  css: string;
  props?: ComponentProp[];
  kind?: ComponentKind;
}

export interface UpdateComponentBody {
  title?: string;
  elements?: ZElement[];
  css?: string;
  props?: ComponentProp[];
}

export interface ComponentsApi {
  list(): Promise<ComponentSummary[]>;
  get(id: number): Promise<ComponentData>;
  create(body: CreateComponentBody): Promise<ComponentData>;
  update(id: number, body: UpdateComponentBody): Promise<ComponentData>;
  remove(id: number): Promise<{ deleted: boolean; id: number }>;
}

type Method = 'GET' | 'POST' | 'PUT' | 'DELETE';

export function createRestComponentsApi(config: RestConfig, fetchFn: typeof fetch = (...args) => fetch(...args)): ComponentsApi {
  const base = config.restUrl.replace(/\/$/, '');
  async function call<T>(method: Method, path: string, body?: unknown, retried = false): Promise<T> {
    const res = await fetchFn(`${base}${path}`, {
      method,
      credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json', 'X-WP-Nonce': config.nonce },
      body: body === undefined ? undefined : JSON.stringify(body),
    });
    const text = await res.text();
    let json: any = null;
    try {
      json = text ? JSON.parse(text) : null;
    } catch {
      throw new ApiError(`Invalid JSON from ${path}`, res.status);
    }
    if (!res.ok) {
      const err = new ApiError(json?.message ?? `Request failed (${res.status})`, res.status, json?.code ?? '');
      if (isSessionExpired(err)) {
        if (!retried && (await refreshNonce(config, fetchFn))) return call<T>(method, path, body, true);
        err.message = SESSION_EXPIRED_MESSAGE;
      }
      throw err;
    }
    return json as T;
  }
  return {
    list: async () => (await call<{ items: ComponentSummary[] }>('GET', '/components')).items,
    get: (id) => call('GET', `/components/${id}`),
    create: (body) => call('POST', '/components', body),
    update: (id, body) => call('PUT', `/components/${id}`, body),
    remove: (id) => call('DELETE', `/components/${id}`),
  };
}

let instance: ComponentsApi | null = null;

export function setComponentsApi(api: ComponentsApi | null): void {
  instance = api;
}

export async function getComponentsApi(): Promise<ComponentsApi> {
  if (instance) return instance;
  const config = window.ZADEX;
  if (!config) throw new Error('No config');
  // The dev harness mock is development-only: the production build drops this branch and its chunk.
  if (import.meta.env.DEV && config.restUrl.startsWith('mock:')) {
    const { createMockComponentsApi } = await import('@/dev/mock-components');
    instance = createMockComponentsApi();
  } else {
    instance = createRestComponentsApi(config);
  }
  return instance;
}
