import type { FunctionalComponent } from 'preact';
import { useCallback, useEffect, useMemo, useState } from 'preact/hooks';

import { onAsukaEvent } from '../../../core/events';
import type { AsukaMutationResult } from '../../../core/renderEngine';
import {
  createParametricDefaultValues,
  isParametricExpressionTruthy,
} from '../../../core/parametric3d';
import {
  formatParametricDisplayNumber,
  resolveParametricNumberPresentation,
} from '../../../core/parametricMeasurement';
import type {
  AsukaParametricParameter,
  AsukaParametricProductDescriptor,
  AsukaParametricValue,
} from '../../../types/parametric3d';
import { useAsukaStore } from '../../../store/asukaStore';
import { useT } from '../../../hooks/useT';
import { Button } from '../../core/Button';
import { Field } from '../../core/Field';
import { Input } from '../../core/Input';
import { Select } from '../../core/Select';
import { SectionCard } from '../../core/SectionCard';

export type ProductParameterControlsProps = {
  context: any;
  productId?: string | null;
  parameterIds?: string[];
  title?: string;
  description?: string;
  compact?: boolean;
  showReset?: boolean;
  source?: string;
  className?: string;
  collapsible?: boolean;
  defaultCollapsed?: boolean;
};

const asPrimitive = (value: unknown): AsukaParametricValue => {
  if (value == null || typeof value === 'string' || typeof value === 'boolean') return value as AsukaParametricValue;
  const number = Number(value);
  return Number.isFinite(number) ? number : null;
};

const normalizeProductId = (value: unknown): string => String(value ?? '').trim().replace(/^product:/i, '');

export const ProductParameterControls: FunctionalComponent<ProductParameterControlsProps> = ({
  context,
  productId,
  parameterIds,
  title,
  description,
  compact = true,
  showReset = true,
  source = 'variations.parametric-controls',
  className,
  collapsible = false,
  defaultCollapsed = false,
}) => {
  const t = useT();
  const engine = context?.getEngine?.() as any;
  const elements = useAsukaStore((state: any) => Array.isArray(state.elements) ? state.elements : []);
  const activeViewId = useAsukaStore((state: any) => String(state.activeViewId ?? ''));
  const [revision, setRevision] = useState(0);
  const [drafts, setDrafts] = useState<Record<string, string>>({});
  const [previewDescriptor, setPreviewDescriptor] = useState<AsukaParametricProductDescriptor | null>(null);
  const [expanded, setExpanded] = useState(!defaultCollapsed);

  const api = useMemo(() => {
    return engine?.products ?? null;
  }, [engine]);

  const requestedProductId = normalizeProductId(productId);

  const syncCatalog = useCallback(() => {
    try { api?.syncCatalog(elements); } catch {}
  }, [api, elements]);

  const descriptor = useMemo<AsukaParametricProductDescriptor | null>(() => {
    void revision;
    void activeViewId;
    syncCatalog();
    if (!api) return null;
    return api.getParameters(requestedProductId || undefined) ?? null;
  }, [api, requestedProductId, revision, activeViewId, syncCatalog]);

  const measurementSettings = useMemo(() => {
    void revision;
    void activeViewId;
    try {
      return engine?.assembly?.getState?.()?.settings?.measurement ?? null;
    } catch {
      return null;
    }
  }, [engine, revision, activeViewId]);

  const descriptorValuesKey = JSON.stringify(descriptor?.state?.values ?? {});

  useEffect(() => {
    if (!descriptor) {
      setDrafts({});
      setPreviewDescriptor(null);
      return;
    }
    const next: Record<string, string> = {};
    const parameters = new Map(descriptor.definition.parameters.map((parameter) => [parameter.id, parameter]));
    for (const [id, value] of Object.entries(descriptor.state.values ?? {})) {
      const parameter = parameters.get(id);
      const presentation = parameter
        ? resolveParametricNumberPresentation(parameter, value, descriptor.evaluation.context, measurementSettings)
        : null;
      next[id] = presentation?.measured
        ? formatParametricDisplayNumber(presentation.value, presentation.step)
        : String(value ?? '');
    }
    setDrafts(next);
    setPreviewDescriptor(null);
  }, [descriptor, descriptorValuesKey, measurementSettings]);

  const parameterIdsKey = JSON.stringify(parameterIds ?? []);

  useEffect(() => {
    const refresh = () => setRevision((value) => value + 1);
    const off1 = engine?.events.subscribe((event: any) => {
      if (event.type !== 'viewport') refresh();
    });
    const off2 = onAsukaEvent('after:state.restore', refresh);
    return () => { off1?.(); off2?.(); };
  }, [engine, activeViewId]);

  useEffect(() => {
    if (!collapsible) return;
    setExpanded(!defaultCollapsed);
  }, [collapsible, defaultCollapsed, requestedProductId, parameterIdsKey, title]);

  const preview = useCallback((parameter: AsukaParametricParameter, raw: unknown): AsukaParametricValue => {
    let value: AsukaParametricValue = asPrimitive(raw);
    if (parameter.type === 'number' || parameter.type === 'integer') value = Number(raw);
    if (parameter.type === 'boolean') value = Boolean(raw);
    if (parameter.type === 'select') {
      value = parameter.options?.find((option) => String(option.value) === String(raw))?.value ?? parameter.default ?? null;
    }
    if (api && descriptor?.productId) {
      setPreviewDescriptor(api.validateParameters(descriptor.productId, { [parameter.id]: value }));
    }
    return value;
  }, [api, descriptor?.productId]);

  const commit = useCallback((parameter: AsukaParametricParameter, value: AsukaParametricValue, commitHistory: boolean) => {
    if (!api || !descriptor?.productId) return;
    const result = api.setParameter(descriptor.productId, parameter.id, value, {
      source,
      commitHistory,
      emitChange: true,
      validate: true,
    });
    void Promise.resolve(result).then((mutation: AsukaMutationResult<AsukaParametricProductDescriptor | null>) => {
      const next = mutation.value;
      if (next && !next.evaluation.valid) setPreviewDescriptor(next);
      else setPreviewDescriptor(null);
      if (next) {
        const nextParameter = next.definition.parameters.find((candidate) => candidate.id === parameter.id) ?? parameter;
        const presentation = resolveParametricNumberPresentation(
          nextParameter,
          next.state.values?.[parameter.id] ?? value,
          next.evaluation.context,
          measurementSettings,
        );
        setDrafts((current) => ({
          ...current,
          [parameter.id]: presentation.measured
            ? formatParametricDisplayNumber(presentation.value, presentation.step)
            : String(next.state.values?.[parameter.id] ?? value ?? ''),
        }));
        setRevision((current) => current + 1);
      }
    }).catch((error) => {
      console.error('[AsukaDesigner parametric3d] Unable to apply product parameter from Variations.', error);
      setRevision((current) => current + 1);
    });
  }, [api, descriptor?.productId, source, measurementSettings]);

  const resetParameters = useCallback(() => {
    if (!api || !descriptor?.productId) return;
    const defaults = createParametricDefaultValues(descriptor.definition);
    const allowed = Array.isArray(parameterIds)
      ? Object.fromEntries(Object.entries(defaults).filter(([id]) => parameterIds.includes(id)))
      : defaults;
    const result = api.setParameters(descriptor.productId, allowed, {
      source: `${source}.reset`,
      commitHistory: true,
      emitChange: true,
      validate: true,
    });
    void Promise.resolve(result).then(() => setRevision((current) => current + 1));
  }, [api, descriptor?.productId, descriptor?.definition, parameterIds, source]);

  if (!descriptor) return null;

  const contextValues = previewDescriptor?.evaluation?.context ?? descriptor.evaluation.context;
  const requestedIds = Array.isArray(parameterIds)
    ? parameterIds.map((id) => String(id ?? '').trim()).filter(Boolean)
    : descriptor.definition.parameters.map((parameter) => parameter.id);
  const parameterMap = new Map(descriptor.definition.parameters.map((parameter) => [parameter.id, parameter]));
  const parameters = requestedIds.map((id) => parameterMap.get(id)).filter(Boolean) as AsukaParametricParameter[];
  const issues = previewDescriptor?.evaluation?.issues ?? descriptor.evaluation.issues;
  if (!parameters.length) return null;

  const renderParameter = (parameter: AsukaParametricParameter) => {
    if (!isParametricExpressionTruthy(parameter.visibleWhen, contextValues, true)) return null;
    const enabled = isParametricExpressionTruthy(parameter.enabledWhen, contextValues, true);
    const current = descriptor.state.values[parameter.id];
    const label = parameter.label || parameter.id;

    if (parameter.type === 'boolean') {
      return (
        <label key={parameter.id} className="ask-flex ask-cursor-pointer ask-items-center ask-gap-3 ask-rounded-[16px] ask-border ask-border-borderSubtle/80 ask-bg-surface ask-p-3">
          <input
            type="checkbox"
            className="ask-theme-checkbox"
            checked={Boolean(current)}
            disabled={!enabled}
            onChange={(event: any) => commit(parameter, preview(parameter, !!event.currentTarget?.checked), true)}
          />
          <span className="ask-min-w-0 ask-flex-1">
            <span className="ask-block ask-text-[12px] ask-font-semibold ask-leading-5 ask-text-textMain">{label}</span>
            {parameter.description ? <span className="ask-mt-0.5 ask-block ask-text-[11px] ask-leading-[1.55] ask-text-textMuted">{parameter.description}</span> : null}
          </span>
          <span className="ask-shrink-0 ask-text-[11px] ask-font-medium ask-text-textMuted">
            {current ? t('common.on', 'On') : t('common.off', 'Off')}
          </span>
        </label>
      );
    }

    if (parameter.type === 'select') {
      return (
        <div key={parameter.id} className="ask-rounded-[16px] ask-border ask-border-borderSubtle/80 ask-bg-surface ask-p-3">
          <Field label={label} hint={parameter.description} className="ask-gap-2.5">
            <Select
              value={String(current ?? '')}
              disabled={!enabled}
              className="ask-bg-surfaceAlt"
              options={(parameter.options ?? []).map((option) => ({
                value: String(option.value),
                label: option.label,
                disabled: isParametricExpressionTruthy(option.disabledWhen, contextValues, false),
              }))}
              onChange={(event: any) => commit(parameter, preview(parameter, event.currentTarget?.value), true)}
            />
          </Field>
        </div>
      );
    }

    const presentation = resolveParametricNumberPresentation(
      parameter,
      current,
      contextValues,
      measurementSettings,
    );
    const finiteMin = presentation.min;
    const finiteMax = presentation.max;
    const step = presentation.step;
    const draft = drafts[parameter.id]
      ?? formatParametricDisplayNumber(presentation.value, step);
    const commitDraft = (event?: { currentTarget?: { value?: unknown } }) => {
      const inputValue = event?.currentTarget?.value;
      const displayValue = inputValue != null
        ? String(inputValue)
        : drafts[parameter.id] ?? presentation.value;
      const numeric = Number(displayValue);
      if (String(displayValue ?? '').trim() === '' || !Number.isFinite(numeric)) {
        setDrafts((currentDrafts) => ({
          ...currentDrafts,
          [parameter.id]: formatParametricDisplayNumber(presentation.value, step),
        }));
        return;
      }
      commit(parameter, preview(parameter, presentation.toStoredValue(numeric)), true);
    };
    const nudge = (direction: number) => {
      const next = presentation.value + direction * step;
      commit(parameter, preview(parameter, presentation.toStoredValue(next)), true);
    };

    return (
      <div key={parameter.id} className="ask-rounded-[16px] ask-border ask-border-borderSubtle/80 ask-bg-surface ask-p-3">
        <div className="ask-flex ask-flex-col ask-gap-3">
          <div className="ask-min-w-0">
            <div className="ask-text-[12px] ask-font-semibold ask-leading-5 ask-text-textMain">{label}</div>
            {parameter.description ? <div className="ask-mt-0.5 ask-text-[11px] ask-leading-[1.55] ask-text-textMuted">{parameter.description}</div> : null}
          </div>

          <div className="ask-grid ask-grid-cols-[32px_minmax(0,1fr)_32px] ask-items-center ask-gap-1.5">
            <Button size="sm" variant="secondary" disabled={!enabled} onClick={() => nudge(-1)} className="!ask-h-9 !ask-rounded-full !ask-px-0">−</Button>
            <div className="ask-min-w-0">
              <Input
                type="number"
                min={finiteMin}
                max={finiteMax}
                step={step}
                disabled={!enabled}
                value={draft}
                className="ask-w-full ask-min-w-0 ask-border-0 ask-bg-surfaceAlt ask-px-1 ask-text-center ask-text-[11px] ask-font-semibold ask-shadow-none focus:ask-ring-0"
                onInput={(event: any) => {
                  const raw = String(event.currentTarget?.value ?? '');
                  setDrafts((currentDrafts) => ({ ...currentDrafts, [parameter.id]: raw }));
                  const numeric = Number(raw);
                  if (raw.trim() !== '' && Number.isFinite(numeric)) {
                    commit(parameter, preview(parameter, presentation.toStoredValue(numeric)), false);
                  }
                }}
                onBlur={commitDraft}
                onKeyDown={(event: any) => {
                  if (event.key === 'Enter') {
                    event.currentTarget?.blur?.();
                  }
                }}
              />
              {presentation.unit ? (
                <div className="ask-mt-0.5 ask-text-center ask-text-[9px] ask-font-medium ask-leading-none ask-text-textMuted">
                  {presentation.unit}
                </div>
              ) : null}
            </div>
            <Button size="sm" variant="secondary" disabled={!enabled} onClick={() => nudge(1)} className="!ask-h-8 !ask-rounded-full !ask-px-0">+</Button>
          </div>

          {finiteMin != null && finiteMax != null ? (
            <div className="ask-flex ask-flex-col ask-gap-1.5">
              <input
                type="range"
                min={finiteMin}
                max={finiteMax}
                step={step}
                disabled={!enabled}
                value={presentation.value}
                className="ask-w-full"
                onInput={(event: any) => commit(
                  parameter,
                  preview(parameter, presentation.toStoredValue(Number(event.currentTarget?.value))),
                  false,
                )}
                onChange={(event: any) => commit(
                  parameter,
                  preview(parameter, presentation.toStoredValue(Number(event.currentTarget?.value))),
                  true,
                )}
              />
              <div className="ask-flex ask-items-center ask-justify-between ask-text-[10px] ask-text-textMuted">
                <span>{formatParametricDisplayNumber(finiteMin, step)}{presentation.unit ? ` ${presentation.unit}` : ''}</span>
                <span>{formatParametricDisplayNumber(finiteMax, step)}{presentation.unit ? ` ${presentation.unit}` : ''}</span>
              </div>
            </div>
          ) : null}
        </div>
      </div>
    );
  };

  const resolvedTitle = title || t('parametric3d.variationControlsTitle', 'Dimensions & parameters');
  const resolvedDescription = description || t('parametric3d.variationControlsDescriptionCompact', 'Adjust the selected part.');

  const content = (
    <div className="ask-flex ask-flex-col ask-gap-2.5">
      {issues.length ? (
        <div className="ask-flex ask-flex-col ask-gap-2">
          {issues.map((issue, index) => (
            <div key={`${issue.id ?? 'issue'}-${index}`} className={[
              'ask-rounded-xl ask-border ask-p-2.5 ask-text-[11px] ask-leading-5',
              issue.severity === 'warning' ? 'ask-border-warning/40 ask-bg-warning/10' : 'ask-border-danger/40 ask-bg-danger/10',
            ].join(' ')}>{issue.message}</div>
          ))}
        </div>
      ) : null}
      {parameters.map(renderParameter)}
    </div>
  );

  if (!collapsible) {
    return (
      <SectionCard
        className={className}
        variant="subtle"
        padding={compact ? 'sm' : 'md'}
        title={resolvedTitle}
        description={description || undefined}
        actions={showReset ? (
          <Button size="sm" variant="ghost" onClick={resetParameters}>{t('common.reset', 'Reset')}</Button>
        ) : null}
        contentClassName="ask-flex ask-flex-col ask-gap-3"
      >
        {content}
      </SectionCard>
    );
  }

  return (
    <section className={[
      'ask-overflow-hidden ask-rounded-[18px] ask-border ask-border-borderSubtle/80 ask-bg-surfaceAlt/60',
      className ?? '',
    ].join(' ')}>
      <div className="ask-flex ask-items-start ask-gap-3 ask-p-3">
        <button
          type="button"
          className="ask-flex ask-min-w-0 ask-flex-1 ask-items-start ask-gap-3 ask-text-left"
          onClick={() => setExpanded((value) => !value)}
        >
          <span className="ask-mt-0.5 ask-flex ask-h-8 ask-w-8 ask-shrink-0 ask-items-center ask-justify-center ask-rounded-full ask-bg-primary/8 ask-text-[14px] ask-font-semibold ask-text-primary">
            {expanded ? '−' : '+'}
          </span>
          <span className="ask-min-w-0 ask-flex-1">
            <span className="ask-flex ask-items-center ask-gap-2 ask-flex-wrap">
              <span className="ask-text-[13px] ask-font-semibold ask-leading-5 ask-text-textMain">{resolvedTitle}</span>
              <span className="ask-inline-flex ask-items-center ask-rounded-full ask-bg-surface ask-px-2 ask-py-0.5 ask-text-[10px] ask-font-medium ask-text-textMuted">
                {parameters.length}
              </span>
            </span>
            <span className="ask-mt-0.5 ask-block ask-text-[11px] ask-leading-[1.5] ask-text-textMuted">{resolvedDescription}</span>
          </span>
        </button>

        {expanded && showReset ? (
          <Button size="sm" variant="ghost" onClick={resetParameters} className="!ask-h-8 !ask-px-2.5">
            {t('common.reset', 'Reset')}
          </Button>
        ) : null}
      </div>

      {expanded ? <div className="ask-border-t ask-border-borderSubtle/70 ask-bg-surfaceAlt/40 ask-p-3">{content}</div> : null}
    </section>
  );
};

export default ProductParameterControls;
