import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { Uniwind } from 'uniwind';
import {
  Alert,
  Box,
  Button,
  ListItemIcon,
  ListItemText,
  MenuItem,
  MenuList,
  Popover,
  ToggleButton,
  ToggleButtonGroup,
  Typography,
} from '@mui/material';
import {
  CheckOutlined,
  CodeOutlined,
  ContentCopyOutlined,
  DarkModeOutlined,
  LightModeOutlined,
} from '@mui/icons-material';
import {
  applyCoveringThemeOverride,
  applyOverrideInputTransform,
  buildStaticManagedTargets,
  createThemeOverrideSessionState,
  createThemeOverrideWriterOwnershipToken,
  expandParsedThemeOverride,
  overrideCatalogs,
  overrideRuleEvaluator,
  overrideRuntimeData,
  overrideRuntimeMap,
  parseThemeOverride,
  presetFonts,
  type OverrideDiagnostic,
  type InputDefinition,
  type InputId,
  type Preset,
  type SelectionId,
  type ThemeOverrideEditorDraft,
  type ThemeOverrideSessionState,
  type ThemeSelections,
} from '@cdx-ui/styles';
import type { ThemeEditorProps } from './types';
import { loadPresetFonts, loadPlatformFonts, cleanupFontLinks } from './fonts';
import { ControlRenderer, validateControlWidgetRegistry } from './controls';
import { PreviewPanel } from './PreviewPanel';
import {
  TARGET_CONTROL_DEFINITIONS,
  TARGET_EDITOR_LAYOUT,
  resolveControlDefinitions,
  validateEditorLayout,
  type ResolvedControlDefinition,
} from './override/controls';
import {
  loadThemeOverrideDraft,
  reduceThemeOverrideDraft,
  serializeThemeOverrideDraft,
  type ThemeOverrideDraftAction,
} from './override/draft';
import { evaluateThemeOverrideCandidate, evaluateThemeOverrideDraft } from './override/runtime';
import type { EditorDisplayDefaults, ThemeOverrideEditorRuntime } from './override/types';

type ThemeMode = 'light' | 'dark';

const DEFAULT_BRAND = '#346099';
const DEFAULT_ACCENT = '#ffcf23';
const PALETTE_DISPLAY_FALLBACKS: Readonly<Partial<Record<'brand' | 'accent' | 'base', string>>> = {
  brand: DEFAULT_BRAND,
  accent: DEFAULT_ACCENT,
};

function getDefaultInput(definition: InputDefinition, preset: Preset): string | undefined {
  if (definition.strategy.kind === 'display-font') {
    return presetFonts[preset][0];
  }
  if (definition.strategy.fallback) return undefined;

  const primitivePath = `color.${definition.strategy.category}.input`;
  const primitiveValue = overrideRuntimeData.primitives.byPreset[preset][primitivePath];
  return primitiveValue === undefined
    ? PALETTE_DISPLAY_FALLBACKS[definition.strategy.category]
    : String(primitiveValue);
}

function deriveInput(
  definition: InputDefinition,
  values: Readonly<Partial<Record<InputId, string>>>,
): string | undefined {
  if (definition.strategy.kind !== 'palette' || !definition.strategy.fallback) {
    return undefined;
  }
  const source = values[definition.strategy.fallback.inputId];
  if (!source) return undefined;
  return applyOverrideInputTransform(definition.strategy.fallback.transform, source);
}

const DISPLAY_DEFAULTS: EditorDisplayDefaults = {
  listInputs: () => overrideCatalogs.listInputs(),
  getDefaultInput,
  deriveInput,
};

validateEditorLayout(TARGET_CONTROL_DEFINITIONS, TARGET_EDITOR_LAYOUT);
validateControlWidgetRegistry(TARGET_CONTROL_DEFINITIONS);
const RESOLVED_CONTROLS = resolveControlDefinitions(TARGET_CONTROL_DEFINITIONS, overrideCatalogs);
const CONTROL_BY_ID = new Map(RESOLVED_CONTROLS.map((definition) => [definition.id, definition]));
const STATIC_MANAGED_TARGETS = buildStaticManagedTargets(overrideCatalogs.listManagedTargets());

function adapterDiagnostic(
  message: string,
  mode: 'light' | 'dark',
  artifactRevision: string,
): OverrideDiagnostic {
  return {
    code: 'override.adapter_write_failed',
    severity: 'error',
    phase: 'adapter',
    message,
    mode,
    artifactRevision,
  };
}

function createEditorRuntime(
  writerOwner: ReturnType<typeof createThemeOverrideWriterOwnershipToken>,
): ThemeOverrideEditorRuntime<ThemeOverrideSessionState> {
  return {
    parse: parseThemeOverride,
    expand: expandParsedThemeOverride,
    evaluateCandidate: (request) =>
      overrideRuleEvaluator.evaluateCandidate(overrideRuntimeData.rules, request),
    applyPreview: (expanded, session) => {
      const result = applyCoveringThemeOverride({
        expanded,
        runtimeMap: overrideRuntimeMap,
        writer: (mode, variables) => {
          Uniwind.updateCSSVariables(mode, { ...variables });
        },
        session,
        staticManagedTargets: STATIC_MANAGED_TARGETS,
        writerOwner,
      });
      if (result.applied) {
        return {
          applied: true,
          session: result.session,
        };
      }
      const { failure } = result;
      return {
        applied: false,
        diagnostics: [
          adapterDiagnostic(
            failure.cause instanceof Error
              ? failure.cause.message
              : 'Theme preview could not write runtime variables.',
            failure.mode,
            failure.artifactRevision,
          ),
        ],
      };
    },
  };
}

function loadDraft(initialValue: unknown): ThemeOverrideEditorDraft {
  return loadThemeOverrideDraft({
    raw: initialValue,
    parse: parseThemeOverride,
    defaults: DISPLAY_DEFAULTS,
    previewPlatform: 'web',
  });
}

/**
 * Guided target-contract authoring with intent-only serialization and a
 * stateful covering preview.
 */
export function ThemeEditor({
  initialValue,
  onChange,
  showGetCode = false,
  showThemeModeToggle = false,
}: Readonly<ThemeEditorProps>) {
  const writerOwnerRef = useRef(createThemeOverrideWriterOwnershipToken('theme-editor'));
  const runtime = useMemo(() => createEditorRuntime(writerOwnerRef.current), []);
  const sessionRef = useRef(
    createThemeOverrideSessionState({
      platform: 'web',
      writerOwner: writerOwnerRef.current,
    }),
  );
  const [draft, setDraft] = useState<ThemeOverrideEditorDraft>(() => loadDraft(initialValue));
  const [themeMode, setThemeMode] = useState<ThemeMode>('light');
  const [jsonAnchorEl, setJsonAnchorEl] = useState<HTMLElement | null>(null);
  const [copied, setCopied] = useState(false);
  const debounceRef = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
  const previousInitialValueRef = useRef(initialValue);
  const userHasEditedRef = useRef(false);
  const onChangeRef = useRef(onChange);

  useEffect(() => {
    onChangeRef.current = onChange;
  }, [onChange]);

  useEffect(() => {
    if (previousInitialValueRef.current === initialValue) return;
    previousInitialValueRef.current = initialValue;
    userHasEditedRef.current = false;
    sessionRef.current = createThemeOverrideSessionState({
      platform: 'web',
      writerOwner: writerOwnerRef.current,
    });
    setDraft(loadDraft(initialValue));
  }, [initialValue]);

  useEffect(() => {
    loadPlatformFonts();
    return cleanupFontLinks;
  }, []);

  useEffect(() => {
    loadPresetFonts(draft.basePreset.displayValue);
  }, [draft.basePreset.displayValue]);

  const evaluation = useMemo(() => evaluateThemeOverrideDraft(draft, runtime), [draft, runtime]);
  const serialized = useMemo(() => serializeThemeOverrideDraft(draft), [draft]);
  const diagnostics = evaluation.diagnostics;
  const jsonString = useMemo(
    () =>
      JSON.stringify(
        serialized.valid ? serialized.override : { diagnostics: serialized.diagnostics },
        null,
        2,
      ),
    [serialized],
  );

  useEffect(() => {
    if (debounceRef.current) clearTimeout(debounceRef.current);
    debounceRef.current = setTimeout(() => {
      if (!evaluation.valid) return;
      const application = runtime.applyPreview(evaluation.expanded, sessionRef.current);
      if (!application.applied) return;
      sessionRef.current = application.session;
      if (userHasEditedRef.current) {
        onChangeRef.current(evaluation.override);
      }
    }, 200);

    return () => {
      if (debounceRef.current) clearTimeout(debounceRef.current);
    };
  }, [evaluation, runtime]);

  const dispatch = useCallback(
    (action: ThemeOverrideDraftAction) => {
      userHasEditedRef.current = true;
      setDraft((current) => reduceThemeOverrideDraft(current, action, DISPLAY_DEFAULTS));
    },
    [setDraft],
  );

  const handlePresetChange = useCallback(
    (preset: Preset) => dispatch({ type: 'set-preset', preset }),
    [dispatch],
  );
  const handleInputChange = useCallback(
    (inputId: InputId, value: string) => dispatch({ type: 'set-input', inputId, value }),
    [dispatch],
  );
  const handleInputClear = useCallback(
    (inputId: InputId) => dispatch({ type: 'clear-input', inputId }),
    [dispatch],
  );
  const handleSelectionChange = useCallback(
    (definition: ResolvedControlDefinition, value: NonNullable<ThemeSelections[SelectionId]>) => {
      if (definition.binding.kind !== 'selection') {
        throw new Error(`Editor control "${definition.id}" is not selection-bound.`);
      }
      const { selectionId } = definition.binding;
      const candidate = evaluateThemeOverrideCandidate(draft, selectionId, value, runtime);
      if (candidate.accepted) {
        dispatch({ type: 'set-selection', selectionId, value });
      } else {
        setDraft((current) => ({
          ...current,
          diagnostics: candidate.diagnostics,
        }));
      }
    },
    [dispatch, draft, runtime],
  );
  const handleSelectionClear = useCallback(
    (selectionId: SelectionId) => dispatch({ type: 'clear-selection', selectionId }),
    [dispatch],
  );
  const handleThemeModeChange = useCallback((_event: unknown, value: ThemeMode | null) => {
    if (value) setThemeMode(value);
  }, []);
  const handleCopyJson = useCallback(() => {
    void navigator.clipboard?.writeText(jsonString);
    setCopied(true);
  }, [jsonString]);

  useEffect(() => {
    if (!copied) return;
    const timeout = setTimeout(() => setCopied(false), 1500);
    return () => clearTimeout(timeout);
  }, [copied]);
  const loadError =
    draft.loadState === 'unsupported' || draft.loadState === 'invalid'
      ? diagnostics[0]?.message
      : undefined;

  return (
    <Box sx={{ display: 'flex', height: '100%', p: 3, gap: 3 }}>
      <Box
        sx={{
          display: 'flex',
          flexDirection: 'column',
          border: '1px solid',
          borderColor: 'divider',
          borderRadius: 2,
          minWidth: 320,
          maxWidth: 400,
          overflow: 'auto',
        }}
      >
        <Box sx={{ display: 'flex', flexDirection: 'column', gap: 2, px: 1.5, py: 2 }}>
          {loadError ? <Alert severity="error">{loadError}</Alert> : null}
          {TARGET_EDITOR_LAYOUT.map((section) => (
            <Box
              key={section.id}
              component="section"
              sx={{ display: 'flex', flexDirection: 'column', gap: 2 }}
            >
              {section.heading ? (
                <Typography variant="subtitle2" sx={{ pt: 1 }}>
                  {section.heading}
                </Typography>
              ) : null}
              {section.controlIds.map((controlId) => {
                const definition = CONTROL_BY_ID.get(controlId);
                if (!definition) {
                  throw new Error(
                    `Theme Editor layout references unresolved control "${controlId}".`,
                  );
                }
                return (
                  <ControlRenderer
                    key={definition.id}
                    definition={definition}
                    draft={draft}
                    diagnostics={diagnostics}
                    fontOptions={presetFonts}
                    onPresetChange={handlePresetChange}
                    onInputChange={handleInputChange}
                    onInputClear={handleInputClear}
                    onSelectionChange={handleSelectionChange}
                    onSelectionClear={handleSelectionClear}
                  />
                );
              })}
            </Box>
          ))}
          {diagnostics.length > 0 && !loadError ? (
            <Alert
              severity={
                diagnostics.some(({ severity }) => severity === 'error') ? 'error' : 'warning'
              }
            >
              {diagnostics[0].message}
            </Alert>
          ) : null}
        </Box>

        {showGetCode ? (
          <Box sx={{ borderTop: '1px solid', borderColor: 'divider' }}>
            <MenuList>
              <MenuItem onClick={(event) => setJsonAnchorEl(event.currentTarget)}>
                <ListItemIcon>
                  <CodeOutlined fontSize="small" />
                </ListItemIcon>
                <ListItemText>Get code</ListItemText>
              </MenuItem>
            </MenuList>
          </Box>
        ) : null}

        {showThemeModeToggle ? (
          <Box sx={{ p: 1.5, borderTop: '1px solid', borderColor: 'divider' }}>
            <ToggleButtonGroup
              size="small"
              color="primary"
              exclusive
              value={themeMode}
              onChange={handleThemeModeChange}
              aria-label="Theme mode"
              sx={{ width: '100%' }}
            >
              <ToggleButton value="light" sx={{ gap: 1, width: '100%' }}>
                <LightModeOutlined /> Light
              </ToggleButton>
              <ToggleButton value="dark" sx={{ gap: 1, width: '100%' }}>
                <DarkModeOutlined /> Dark
              </ToggleButton>
            </ToggleButtonGroup>
          </Box>
        ) : null}
      </Box>

      <Box
        sx={{
          display: 'flex',
          flexDirection: 'column',
          flex: 1,
          minWidth: 0,
          border: '1px solid',
          borderColor: 'divider',
          borderRadius: 2,
          overflow: 'hidden',
          maxHeight: '100%',
        }}
      >
        <PreviewPanel theme={themeMode} />
      </Box>

      <Popover
        open={Boolean(jsonAnchorEl)}
        anchorEl={jsonAnchorEl}
        onClose={() => setJsonAnchorEl(null)}
        anchorOrigin={{ vertical: 'center', horizontal: 'right' }}
        transformOrigin={{ vertical: 'center', horizontal: 'left' }}
        slotProps={{ paper: { sx: { ml: 1 } } }}
      >
        <Box sx={{ width: 380, maxHeight: 480, display: 'flex', flexDirection: 'column' }}>
          <Box
            sx={{
              display: 'flex',
              alignItems: 'center',
              justifyContent: 'space-between',
              px: 2,
              py: 1,
              borderBottom: '1px solid',
              borderColor: 'divider',
            }}
          >
            <Typography variant="subtitle2">Theme Override JSON</Typography>
            <Button
              size="small"
              startIcon={
                copied ? (
                  <CheckOutlined fontSize="small" />
                ) : (
                  <ContentCopyOutlined fontSize="small" />
                )
              }
              onClick={handleCopyJson}
            >
              {copied ? 'Copied' : 'Copy'}
            </Button>
          </Box>
          <Box
            component="pre"
            sx={{
              m: 0,
              px: 2,
              py: 1.5,
              overflow: 'auto',
              fontFamily: 'monospace',
              fontSize: 12,
              lineHeight: 1.5,
              whiteSpace: 'pre',
            }}
          >
            {jsonString}
          </Box>
        </Box>
      </Popover>
    </Box>
  );
}
