import { useCallback, useEffect, useMemo, useState } from 'react';
import {
  MenuEditor,
  MenuProvider,
  useMenuClient,
  type EntityTarget,
  type MenuClient,
  type MenuRow,
  type MenuTarget,
  type MenuTree,
} from '@platform-modules/menus-react';
import type { MenuItemRow } from '@platform-modules/menus';
import { Button } from '@platform-modules/ui-primitives';
import { DEFAULT_PRIMARY } from '../../lib/menus.js';
import { makeContentClient } from '../../lib/clients.js';
import { ScreenHeader } from './ScreenHeader.js';

type ApiErrorBody = { error?: { message?: string } };

async function menusApiFetch<T>(url: string, init?: RequestInit): Promise<T> {
  const headers = new Headers(init?.headers);
  if (init?.body != null && !headers.has('Content-Type')) {
    headers.set('Content-Type', 'application/json');
  }
  if (init?.method && init.method !== 'GET' && !headers.has('origin')) {
    headers.set('origin', window.location.origin);
  }
  const res = await fetch(url, { ...init, credentials: 'same-origin', headers });
  const body: unknown = await res.json().catch(() => null);
  if (!res.ok) {
    const err = body as ApiErrorBody | null;
    throw new Error(err?.error?.message ?? res.statusText);
  }
  return body as T;
}

function makeMenuClient(): MenuClient {
  return {
    async listMenus(): Promise<MenuRow[]> {
      const data = await menusApiFetch<{ menus: MenuRow[] }>('/api/admin/menus');
      return data.menus;
    },
    async getMenuTree(key: string): Promise<MenuTree | null> {
      const data = await menusApiFetch<{ tree: MenuTree | null }>(
        `/api/admin/menus?key=${encodeURIComponent(key)}`,
      );
      return data.tree;
    },
    async createMenu(input: { key: string; label: string }): Promise<MenuRow> {
      const data = await menusApiFetch<{ menu: MenuRow }>('/api/admin/menus', {
        method: 'POST',
        body: JSON.stringify({ action: 'createMenu', key: input.key, label: input.label }),
      });
      return data.menu;
    },
    async updateMenu(menuId: string, patch: { label?: string }): Promise<MenuRow> {
      const data = await menusApiFetch<{ menu: MenuRow }>('/api/admin/menus', {
        method: 'PATCH',
        body: JSON.stringify({ resource: 'menu', menuId, patch }),
      });
      return data.menu;
    },
    async deleteMenu(menuId: string): Promise<void> {
      await menusApiFetch('/api/admin/menus', {
        method: 'DELETE',
        body: JSON.stringify({ resource: 'menu', menuId }),
      });
    },
    async addItem(
      menuId: string,
      input: { label: string; target: MenuTarget; parentId?: string | null },
    ): Promise<MenuItemRow> {
      const data = await menusApiFetch<{ item: MenuItemRow }>('/api/admin/menus', {
        method: 'POST',
        body: JSON.stringify({
          action: 'addItem',
          menuId,
          label: input.label,
          target: input.target,
          ...(input.parentId ? { parentId: input.parentId } : {}),
        }),
      });
      return data.item;
    },
    async updateItem(
      itemId: string,
      patch: { label?: string; target?: MenuTarget; openInNew?: boolean },
    ): Promise<MenuItemRow> {
      const data = await menusApiFetch<{ item: MenuItemRow }>('/api/admin/menus', {
        method: 'PATCH',
        body: JSON.stringify({ resource: 'item', itemId, patch }),
      });
      return data.item;
    },
    async removeItem(itemId: string): Promise<void> {
      await menusApiFetch('/api/admin/menus', {
        method: 'DELETE',
        body: JSON.stringify({ resource: 'item', itemId }),
      });
    },
    async moveItem(
      itemId: string,
      input: { newParentId: string | null; newPosition: number },
    ): Promise<MenuItemRow> {
      const data = await menusApiFetch<{ item: MenuItemRow }>('/api/admin/menus', {
        method: 'POST',
        body: JSON.stringify({ action: 'moveItem', itemId, ...input }),
      });
      return data.item;
    },
    async reorder(menuId: string, parentId: string | null, orderedIds: string[]): Promise<void> {
      await menusApiFetch('/api/admin/menus', {
        method: 'POST',
        body: JSON.stringify({ action: 'reorderItems', menuId, parentId, orderedIds }),
      });
    },
  };
}

function MenuEditorBody({ availableTargets }: { availableTargets: EntityTarget[] }) {
  const client = useMenuClient();
  const [seeding, setSeeding] = useState(false);
  const [seedError, setSeedError] = useState<string | null>(null);
  const [hasPrimary, setHasPrimary] = useState<boolean | null>(null);

  const refreshPrimaryExists = useCallback(async () => {
    const tree = await client.getMenuTree('primary');
    setHasPrimary(tree != null);
  }, [client]);

  useEffect(() => {
    void refreshPrimaryExists();
  }, [refreshPrimaryExists]);

  const seedPrimaryFromDefaults = useCallback(async () => {
    setSeeding(true);
    setSeedError(null);
    try {
      const menu = await client.createMenu({ key: 'primary', label: 'Primary' });
      for (const item of DEFAULT_PRIMARY) {
        await client.addItem(menu.id, {
          label: item.label,
          target: { kind: 'url', url: item.href },
          parentId: null,
        });
      }
      setHasPrimary(true);
      window.location.reload();
    } catch (error) {
      setSeedError(error instanceof Error ? error.message : 'Could not create primary menu.');
    } finally {
      setSeeding(false);
    }
  }, [client]);

  return (
    <>
      {hasPrimary === false ? (
        <div className="mb-4 flex flex-col gap-2">
          <Button
            tone="surface"
            radius="sm"
            size="sm"
            type="button"
            disabled={seeding}
            onClick={() => void seedPrimaryFromDefaults()}
          >
            Create primary menu from defaults
          </Button>
          {seedError ? (
            <p role="alert" className="text-sm text-fg-muted">
              {seedError}
            </p>
          ) : null}
        </div>
      ) : null}
      <MenuEditor menuKey="primary" availableTargets={availableTargets} />
    </>
  );
}

export default function MenuEditorScreen() {
  const client = useMemo(() => makeMenuClient(), []);
  const [availableTargets, setAvailableTargets] = useState<EntityTarget[]>([]);

  useEffect(() => {
    let cancelled = false;
    void makeContentClient()
      .list({ status: 'published' })
      .then((entries) => {
        if (cancelled) return;
        setAvailableTargets(
          entries.map((entry) => ({
            entityType: 'content',
            entityId: entry.id,
            label: entry.title,
          })),
        );
      })
      .catch(() => {
        if (!cancelled) setAvailableTargets([]);
      });
    return () => {
      cancelled = true;
    };
  }, []);

  return (
    <section aria-labelledby="menus-heading">
      <ScreenHeader
        headingId="menus-heading"
        title="Menus"
        description="Manage site navigation links for the public header."
      />

      <MenuProvider client={client}>
        <MenuEditorBody availableTargets={availableTargets} />
      </MenuProvider>
    </section>
  );
}
