import {
  createContext,
  useCallback,
  useContext,
  useMemo,
  useRef,
  useState,
  type ReactNode
} from "react";
import {
  applyConflictChoices,
  type ConflictChoice,
  type ThreeWayMergeResult
} from "../api/concurrency";
import { usePlatformLanguage } from "../i18n/LanguageContext";
import Button from "./Button";
import Dialog from "./Dialog";
import SegmentedControl from "./SegmentedControl";

export type ConcurrencyResolution<T = unknown> =
  | { action: "apply"; value: T; resolvedPaths: string[] }
  | { action: "reload" }
  | { action: "cancel" };

export type ConcurrencyResolutionRequest<T = unknown> = {
  resourceLabel: string;
  merge: ThreeWayMergeResult<T>;
};

type Resolver = <T>(
  request: ConcurrencyResolutionRequest<T>
) => Promise<ConcurrencyResolution<T>>;

type PendingResolution = {
  request: ConcurrencyResolutionRequest;
  resolve: (resolution: ConcurrencyResolution) => void;
};

const ConcurrencyResolverContext = createContext<Resolver | null>(null);

export function ConcurrencyConflictProvider({ children }: { children: ReactNode }) {
  const [pending, setPending] = useState<PendingResolution | null>(null);
  const pendingRef = useRef<PendingResolution | null>(null);

  const requestResolution = useCallback<Resolver>((request) => {
    pendingRef.current?.resolve({ action: "cancel" });
    return new Promise((resolve) => {
      const next: PendingResolution = {
        request: request as ConcurrencyResolutionRequest,
        resolve: resolve as (resolution: ConcurrencyResolution) => void
      };
      pendingRef.current = next;
      setPending(next);
    });
  }, []);

  const finish = useCallback((resolution: ConcurrencyResolution) => {
    const active = pendingRef.current;
    pendingRef.current = null;
    setPending(null);
    active?.resolve(resolution);
  }, []);

  return (
    <ConcurrencyResolverContext.Provider value={requestResolution}>
      {children}
      <ConcurrencyConflictDialog
        request={pending?.request ?? null}
        onResolve={finish}
      />
    </ConcurrencyResolverContext.Provider>
  );
}

export function useConcurrencyConflictResolver(): Resolver {
  const resolver = useContext(ConcurrencyResolverContext);
  if (!resolver) {
    throw new Error(
      "useConcurrencyConflictResolver must be used inside ConcurrencyConflictProvider"
    );
  }
  return resolver;
}

function ConcurrencyConflictDialog({
  request,
  onResolve
}: {
  request: ConcurrencyResolutionRequest | null;
  onResolve: (resolution: ConcurrencyResolution) => void;
}) {
  const { translateText } = usePlatformLanguage();
  const [choices, setChoices] = useState<Record<string, ConflictChoice>>({});
  const requestKey = request
    ? request.merge.conflicts.map((conflict) => `${conflict.path}:${conflict.kind}`).join("|")
    : "";
  const effectiveChoices = useMemo(
    () => Object.fromEntries(
      (request?.merge.conflicts ?? []).map((conflict) => [
        conflict.path,
        choices[conflict.path] ?? "current"
      ])
    ),
    [choices, request, requestKey]
  );

  function applyResolution() {
    if (!request) return;
    onResolve({
      action: "apply",
      value: applyConflictChoices(request.merge, effectiveChoices),
      resolvedPaths: request.merge.conflicts.map((conflict) => conflict.path)
    });
    setChoices({});
  }

  function finish(action: "reload" | "cancel") {
    onResolve({ action });
    setChoices({});
  }

  return (
    <Dialog
      open={Boolean(request)}
      title="Concurrent changes"
      role="alertdialog"
      className="concurrency-conflict-dialog"
      footerClassName="button-row compact-actions"
      onClose={() => finish("cancel")}
      footer={
        <>
          <Button type="button" onClick={() => finish("cancel")}>
            {translateText("i18n:govoplan-core.cancel.77dfd213")}
          </Button>
          <Button type="button" onClick={() => finish("reload")}>
            {translateText("i18n:govoplan-core.reload.cce71553")}
          </Button>
          <Button type="button" variant="primary" onClick={applyResolution}>
            Apply resolution
          </Button>
        </>
      }
    >
      <p>
        {request?.resourceLabel ?? "This item"} changed after you loaded it.
        Review each overlapping change before saving.
      </p>
      <div className="concurrency-conflict-list">
        {(request?.merge.conflicts ?? []).map((conflict) => (
          <section className="concurrency-conflict-item" key={`${requestKey}:${conflict.path}`}>
            <div className="concurrency-conflict-heading">
              <code>{conflict.path}</code>
              <span>{conflictKindLabel(conflict.kind)}</span>
            </div>
            <div className="concurrency-conflict-values">
              <ConflictValue label="Current" value={conflict.currentValue} />
              <ConflictValue label="Your change" value={conflict.localValue} />
            </div>
            <SegmentedControl
              value={effectiveChoices[conflict.path] ?? "current"}
              options={[
                { id: "current", label: "Keep current" },
                { id: "local", label: "Use my change" }
              ]}
              onChange={(value) => setChoices((current) => ({
                ...current,
                [conflict.path]: value as ConflictChoice
              }))}
            />
          </section>
        ))}
      </div>
    </Dialog>
  );
}

function ConflictValue({ label, value }: { label: string; value: unknown }) {
  return (
    <div>
      <strong>{label}</strong>
      <pre>{previewValue(value)}</pre>
    </div>
  );
}

function previewValue(value: unknown): string {
  if (value === undefined) return "(removed)";
  if (typeof value === "string") return value.slice(0, 500);
  try {
    const serialized = JSON.stringify(value, null, 2);
    return serialized.length > 1000
      ? `${serialized.slice(0, 1000)}\n...`
      : serialized;
  } catch {
    return String(value);
  }
}

function conflictKindLabel(kind: string): string {
  return kind.replace(/_/g, " ");
}

export default ConcurrencyConflictDialog;
