import type { FunctionalComponent } from 'preact';
import { useCallback, useEffect, useMemo, useRef, useState } from 'preact/hooks';
import {
  getPrintingMethods,
  getActivePrintingMethodId,
  setActivePrintingMethodId,
  getPaletteForActiveMethodOrFallback,
  subscribePrintingConfig,
  type PrintingMethod,
  type PrintingColor
} from '../../core/printing';
import { registerModule, type AsukaModuleComponent } from '../../core/modules';
import { getI18n } from '../../core/i18n';
import { useT } from '../../hooks/useT';
import { useAsukaStore } from '../../store/asukaStore';
import { DEFAULT_PERMISSIONS } from '../../core/permissions';
import { Card, CardContent, CardHeader, CardTitle } from '../core/Card';
import { Field } from '../core/Field';
import { Select, type SelectOption } from '../core/Select';
import { ColorSwatchGrid, type ColorSwatch } from '../core/ColorSwatchGrid';
import { ColorInput } from '../forms';

function isValidUrl(value: string): boolean {
  try {

    new URL(value);
    return true;
  } catch {
    return false;
  }
}

function cssBgFromValue(value: string): string {
  const v = String(value ?? '').trim();
  if (!v) return '';
  if (isValidUrl(v)) {
    return `url("${v}") center/cover no-repeat`;
  }
  return v;
}


function normalizeStoreColors(items: any[]): PrintingColor[] {
  return (Array.isArray(items) ? items : [])
    .map((c: any, index: number) => {
      const value = String(c?.value ?? c?.color ?? c?.hex ?? '').trim();
      if (!value) return null;
      return {
        id: String(c?.id ?? `color-${index}`),
        label: String(c?.label ?? c?.name ?? value),
        value,
      } as PrintingColor;
    })
    .filter(Boolean) as PrintingColor[];
}


/**
 * ColorsModule (migrated to core components)
 * - Keeps original behavior:
 *   - choose active printing method
 *   - shows current global scope (object / canvas / document)
 *   - pick a palette color to apply to the active object
 */
const ColorsModule: AsukaModuleComponent = ({ context }) => {
  const permissions = useAsukaStore((s: any) => s.permissions ?? DEFAULT_PERMISSIONS);
  const configuredColors = useAsukaStore((s: any) => Array.isArray((s as any).colors) ? (s as any).colors : []);
  const t = useT();
  const [methods, setMethods] = useState<PrintingMethod[]>([]);
  const [activeMethodId, setActiveMethodIdState] = useState<string | null>(null);
  const [palette, setPalette] = useState<PrintingColor[]>([]);
  const [currentFill, setCurrentFill] = useState<string | null>(null);
  const pendingFill = useRef<string | null>(null);

  // SVG parts selection (for grouped SVGs)
  const [activeIsGroup, setActiveIsGroup] = useState<boolean>(false);
  const [groupParts, setGroupParts] = useState<Array<{ index: number; fill: string }>>([]);
  const [selectedPartIndex, setSelectedPartIndex] = useState<number | null>(null);
  const [groupStep, setGroupStep] = useState<'parts' | 'palette'>('parts');

  const directPalette = useMemo(() => normalizeStoreColors(configuredColors as any[]), [configuredColors]);
  const methodsWithColors = useMemo(() => (methods || []).filter((m) => Array.isArray(m.colors) && m.colors.length > 0), [methods]);
  const hasPrintingPalettes = methodsWithColors.length > 0;
  const engine = context.getEngine?.() ?? null;
  const colorTools = engine?.canvas2d ?? null;

  const getSelected3DObject = useCallback(
    () => engine?.objects.list().find((obj: any) => obj?.selected) ?? null,
    [engine],
  );

  // Initialize and subscribe to printing configuration changes (method + scope)
  useEffect(() => {
    setMethods(getPrintingMethods());
    setActiveMethodIdState(getActivePrintingMethodId());

    const unsubscribe = subscribePrintingConfig(() => {
      setMethods(getPrintingMethods());
      setActiveMethodIdState(getActivePrintingMethodId());
    });

    return () => {
      unsubscribe?.();
    };
  }, []);

  useEffect(() => {
    if (hasPrintingPalettes) {
      setPalette(getPaletteForActiveMethodOrFallback());
      return;
    }
    setPalette(directPalette);
  }, [hasPrintingPalettes, directPalette, methods, activeMethodId]);

  // Read the active object's color when the module opens
  useEffect(() => {
    const sync2D = () => {
      const selection = colorTools?.getColorSelection() ?? null;
      const parts = selection?.parts ?? [];
      const isGroup = parts.length > 0;
      setActiveIsGroup(isGroup);
      setGroupParts(parts);
      if (!isGroup) {
        setGroupStep('palette');
        setSelectedPartIndex(null);
        setCurrentFill(pendingFill.current ?? selection?.fill ?? null);
        return;
      }
      setSelectedPartIndex((previous) => (
        typeof previous === 'number' && previous >= 0 && previous < parts.length ? previous : null
      ));
      const selectedFill = typeof selectedPartIndex === 'number' ? parts[selectedPartIndex]?.fill : null;
      setCurrentFill(pendingFill.current ?? selectedFill ?? selection?.fill ?? null);
    };

    const sync3D = () => {
      const active3D: any = getSelected3DObject();
      setActiveIsGroup(false);
      setGroupParts([]);
      setSelectedPartIndex(null);
      setGroupStep('palette');
      if (!active3D) {
        setCurrentFill(null);
        return;
      }
      const source = active3D?.metadata?.source ?? {};
      const fill = String(source?.color ?? active3D?.metadata?.color ?? '').trim();
      const objectMethodId = String(active3D?.pricing?.printingMethodId ?? active3D?.metadata?.printingMethodId ?? '').trim();
      if (objectMethodId) {
        setActivePrintingMethodId(objectMethodId);
        setActiveMethodIdState(objectMethodId);
      }
      setCurrentFill(fill || null);
    };

    const sync = colorTools ? sync2D : sync3D;
    sync();
    const off = engine?.events.subscribe((event) => {
      if (event.type !== 'viewport') sync();
    });
    return () => { off?.(); };
  }, [colorTools, engine, selectedPartIndex, getSelected3DObject]);

  const activeMethod = useMemo(() => {
    if (!activeMethodId) return null;
    return methodsWithColors.find((m) => m.id === activeMethodId) ?? null;
  }, [methodsWithColors, activeMethodId]);

  const methodOptions: SelectOption[] = useMemo(
    () => [
      { value: '', label: t('printing.allMethods', 'All methods') },
      ...methodsWithColors.map((m) => ({ value: m.id, label: m.label })),
    ],
    [methodsWithColors, t]
  );


  const handleMethodChange = (event: Event) => {
    const target = event.currentTarget as HTMLSelectElement;
    const id = target.value || null;

    // Update the active printing method globally...
    setActivePrintingMethodId(id);
    setActiveMethodIdState(id);

    if (colorTools) {
      void colorTools.updateColorSelection({ printingMethodId: id }).catch(() => {});
    } else {
      const active3D: any = getSelected3DObject();
      const objectId = String(active3D?.id ?? '').trim();
      if (engine && objectId) void engine.objects.update(objectId, { printingMethodId: id } as any).catch(() => {});
    }
  };

  // NOTE: Color scope is configured globally (init/API). The Colors module is read-only for this value.

  const handleColorClick = async (color: PrintingColor) => {
    if (!(permissions as any)?.canUseColors) {
      return;
    }

    if (!colorTools) {
      const active3D: any = getSelected3DObject();
      const id = String(active3D?.id ?? '').trim();
      if (engine && id) {
        pendingFill.current = color.value;
        setCurrentFill(color.value);
        try {
          await engine.objects.update(id, { color: color.value, printingMethodId: activeMethodId ?? null } as any);
        } finally {
          setCurrentFill(color.value);
          pendingFill.current = null;
        }
      }
      return;
    }

    if (activeIsGroup) {
      if (typeof selectedPartIndex !== 'number') return;
      pendingFill.current = color.value;
      setCurrentFill(color.value);
      try {
        await colorTools.updateColorSelection({
          fill: color.value,
          partIndex: selectedPartIndex,
          printingMethodId: activeMethodId ?? null,
        });
        setCurrentFill(color.value);
        setGroupParts(colorTools.getColorSelection()?.parts ?? []);
      } finally {
        pendingFill.current = null;
      }
      return;
    }

    pendingFill.current = color.value;
    setCurrentFill(color.value);
    try {
      await colorTools.updateColorSelection({ fill: color.value, printingMethodId: activeMethodId ?? null });
      setCurrentFill(color.value);
    } finally {
      pendingFill.current = null;
    }
  };

  const handleDirectColorInput = async (value: string) => {
    const v = String(value ?? '').trim();
    if (!v) return;
    await handleColorClick({ id: `custom-${v}`, label: v, value: v });
  };

  const swatches: ColorSwatch[] = useMemo(
    () => palette.map((c) => ({ id: c.id, label: c.label, value: cssBgFromValue(c.value) })),
    [palette]
  );

  const partSwatches: ColorSwatch[] = useMemo(() => {
    return groupParts.map((p) => ({
      id: `part-${p.index}`,
      label: `Path ${p.index + 1}`,
      value: cssBgFromValue(p.fill) || '#000000',
    }));
  }, [groupParts]);

  return (
    <Card>
      <CardHeader className="ask-flex ask-items-center ask-justify-between">
        <CardTitle>{t('modules.colors')}</CardTitle>
      </CardHeader>

      <CardContent className="ask-flex ask-flex-col ask-gap-4">
        {hasPrintingPalettes ? (
          <Field
            label={t('ui.printing_method')}
            right={
              activeMethod ? (
                <span className="ask-text-[11px] ask-text-textMuted">{activeMethod.label}</span>
              ) : null
            }
          >
            <Select
              options={methodOptions}
              value={activeMethodId ?? ''}
              onChange={handleMethodChange as any}
            />
          </Field>
        ) : null}


        {activeIsGroup ? (
          groupStep === 'parts' ? (
            <Field label={t('ui.svg_parts')}>
              {partSwatches.length === 0 ? (
                <p className="ask-text-[11px] ask-text-textMuted">{t('ui.no_sub_paths_detected_for_the_selected_group')}</p>
              ) : (
                <ColorSwatchGrid
                  colors={partSwatches}
                  activeId={typeof selectedPartIndex === 'number' ? `part-${selectedPartIndex}` : undefined}
                  onChange={(c) => {
                    const id = String(c.id ?? '');
                    const idx = Number(id.replace('part-', ''));
                    if (Number.isFinite(idx)) {
                      setSelectedPartIndex(idx);
                      const fill = groupParts[idx]?.fill;
                      setCurrentFill(fill || null);
                      setGroupStep('palette');
                    }
                  }}
                />
              )}
              <p className="ask-mt-1 ask-text-[11px] ask-text-textMuted">
                {t('colors.selectSubPathToEdit', 'Select a sub-path to edit.')}
              </p>
            </Field>
          ) : (
            <Field
              label={t('ui.palette')}
              right={
                <button
                  type="button"
                  class="ask-theme-btn ask-theme-btn-secondary ask-text-[11px] ask-inline-flex ask-items-center ask-gap-1 ask-rounded-full ask-border ask-px-2 ask-py-0.5"
                  onClick={() => setGroupStep('parts')}
                >
                  <span class="ask-text-xs">←</span>
                  <span>{t('common.back', 'Back')}</span>
                </button>
              }
            >
              {swatches.length === 0 ? (
                <div class="ask-flex ask-flex-col ask-gap-2">
                  <ColorInput
                    value={currentFill && !isValidUrl(currentFill) ? currentFill : '#000000'}
                    colorLabel={t('common.color', 'Color')}
                    onValueChange={handleDirectColorInput}
                  />
                  <p className="ask-text-[11px] ask-text-textMuted">{t('colors.useColorPickerNoPalette', 'Use the color picker when no palette is configured.')}</p>
                </div>
              ) : (
                <ColorSwatchGrid
                  colors={swatches}
                  activeId={palette.find((p: any) => cssBgFromValue(p?.value) === cssBgFromValue(currentFill ?? ''))?.id}
                  onChange={(c) => {
                    const found = palette.find((p) => p.id === c.id);
                    if (found) handleColorClick(found);
                  }}
                />
              )}
            </Field>
          )
        ) : (
          <Field label={t('ui.palette')}>
            {swatches.length === 0 ? (
              <div class="ask-flex ask-flex-col ask-gap-2">
                <ColorInput
                  value={currentFill && !isValidUrl(currentFill) ? currentFill : '#000000'}
                  colorLabel={t('common.color', 'Color')}
                  onValueChange={handleDirectColorInput}
                />
                <p className="ask-text-[11px] ask-text-textMuted">{t('colors.useColorPickerNoPalette', 'Use the color picker when no palette is configured.')}</p>
              </div>
            ) : (
              <ColorSwatchGrid
                colors={swatches}
                activeId={palette.find((p: any) => cssBgFromValue(p?.value) === cssBgFromValue(currentFill ?? ''))?.id}
                onChange={(c) => {
                  const found = palette.find((p) => p.id === c.id);
                  if (found) handleColorClick(found);
                }}
              />
            )}
          </Field>
        )}
      </CardContent>
    </Card>
  );
};

// Register in the global registry
registerModule({
  id: 'colors',
  label: getI18n().t('modules.colors'),
  icon: 'color',
  displayMode: 'floating',
  supportedEngines: ['fabric', '3d'],
  requiredCapabilities: ['color'],
  render: ColorsModule as AsukaModuleComponent,
});

export default ColorsModule as FunctionalComponent;
