import type { DomEditSelection } from "../components/editor/domEditing";
import { StudioSaveHttpError } from "../utils/studioSaveDiagnostics";
import type { PatchOperation } from "../utils/sourcePatcher";

export class DomEditPersistUnresolvableError extends Error {
  constructor(targetPath: string) {
    super(`Couldn't find this element in the source file (${targetPath})`);
    this.name = "DomEditPersistUnresolvableError";
  }
}

export class DomEditPersistUnsafeValueError extends Error {
  readonly alreadyToasted: boolean;

  constructor(message: string, options: { alreadyToasted?: boolean } = {}) {
    super(message);
    this.name = "DomEditPersistUnsafeValueError";
    this.alreadyToasted = options.alreadyToasted ?? false;
  }
}

export class DomEditPersistUnsupportedTextStructureError extends Error {
  constructor() {
    super("Couldn't save this text structure change");
    this.name = "DomEditPersistUnsupportedTextStructureError";
  }
}

export type DomEditPersistFailureSelection = Pick<
  DomEditSelection,
  "label" | "hfId" | "id" | "selector" | "selectorIndex" | "sourceFile"
>;

function summarizeOperations(operations: PatchOperation[]): string {
  return operations.map((op) => `${op.type}:${op.property}`).join(", ");
}

function getTargetTuple(selection: DomEditPersistFailureSelection) {
  return {
    hfId: selection.hfId,
    id: selection.id,
    selector: selection.selector,
    selectorIndex: selection.selectorIndex,
    sourceFile: selection.sourceFile,
  };
}

function getErrorDetail(error: unknown): string {
  return error instanceof Error ? error.message : String(error);
}

function getSelectionLabel(selection: DomEditPersistFailureSelection): string {
  return selection.label || selection.selector || selection.id || "this element";
}

export function reportDomEditPersistFailure(
  selection: DomEditPersistFailureSelection,
  operations: PatchOperation[],
  error: unknown,
  showToast: (message: string, tone?: "error" | "info") => void,
): void {
  const detail = getErrorDetail(error);
  console.warn("[Studio] DOM edit persist failed", {
    target: getTargetTuple(selection),
    operations: summarizeOperations(operations),
    error: detail,
  });

  const wasAlreadyToasted =
    (error instanceof DomEditPersistUnsafeValueError || error instanceof StudioSaveHttpError) &&
    error.alreadyToasted;
  if (wasAlreadyToasted) {
    return;
  }

  showToast(`Couldn't save "${getSelectionLabel(selection)}": ${detail}`, "error");
}

export function warnDomEditPersistNoOp(
  selection: DomEditPersistFailureSelection,
  operations: PatchOperation[],
): void {
  console.warn("[Studio] DOM edit persist no-op", {
    target: getTargetTuple(selection),
    operations: summarizeOperations(operations),
    detail:
      "Server matched the target but reported no change even though the client believed the value changed.",
  });
}
