import type {
  DiscoveredControl,
  DiscoveredControlCoverageOutcome,
  InteractionCoverageEvidence,
  InteractionCoverageKind,
  InteractionCoverageWitnessRef,
} from "./types.js";
import {
  assessIdempotentTerminalState,
  type IdempotentTerminalAssessment,
  type TerminalInteractionCoverageKind,
} from "./idempotent-state.js";

export type {
  DiscoveredControlCoverageOutcome,
  InteractionCoverageEvidence,
  InteractionCoverageKind,
  InteractionCoverageObligation,
  InteractionCoverageSerialization,
  InteractionCoverageWitnessRef,
} from "./types.js";
export type { IdempotentTerminalAssessment, TerminalInteractionCoverageKind } from "./idempotent-state.js";
export { assessIdempotentTerminalState } from "./idempotent-state.js";

export type DiscoveredControlCoverageEvidence = InteractionCoverageEvidence;

export type InteractionOracleBand = "blocking" | "blocking-conditional" | "advisory";

export type OracleCoverageGapReason =
  | "no-discoverable-contract"
  | InteractionCoverageKind
  | TerminalInteractionCoverageKind;

export type DiscoveredControlOracle =
  | { kind: "aria-pressed-state-flip"; band: "blocking" }
  | { kind: "aria-declared-state"; band: "advisory"; state: string }
  | { kind: "required-form-validation"; band: "blocking-conditional" }
  | { kind: "dialog-disclosure-focus"; band: "advisory" }
  | { kind: "search-landmark-submit"; band: "advisory" }
  | { kind: "same-origin-link-navigation"; band: "advisory" }
  | {
      kind: "coverage-gap";
      reason: OracleCoverageGapReason;
      terminal?: { axis: string; value: "true" | "false" };
    };

export type DiscoveredControlOracleObservation = {
  before?: string;
  after?: string;
  checkValidity?: boolean;
  invalidFired?: boolean;
  nativeSubmissionSuppressed?: boolean;
  explicitStateContradiction?: boolean;
  ariaModal?: boolean;
  backgroundOperable?: boolean;
  beforeOrigin?: string;
  afterOrigin?: string;
  navigated?: boolean;
  reacted?: boolean;
};

type TerminalObligation = {
  id: string;
  witnessRefs: [InteractionCoverageWitnessRef, ...InteractionCoverageWitnessRef[]];
};

type TerminalOutcome<Status extends string, Kind extends TerminalInteractionCoverageKind> = {
  status: Status;
  reason: Kind;
  phase: "terminal";
  evidence: {
    kind: Kind;
    phase: "terminal";
    obligations: [TerminalObligation, ...TerminalObligation[]];
  };
  witnessRefs: [InteractionCoverageWitnessRef, ...InteractionCoverageWitnessRef[]];
};

export type TerminalDiscoveredControlCoverageOutcome = TerminalOutcome<
  "coverage-incomplete",
  Exclude<TerminalInteractionCoverageKind, "satisfied-terminal-state">
>;

export type SatisfiedTerminalEvidence = TerminalOutcome<
  "terminal-evidence",
  "satisfied-terminal-state"
>;

export type DiscoveredControlOracleResult =
  | { kind: "pass"; terminal?: SatisfiedTerminalEvidence }
  | {
      kind: "unproven";
      oracle: DiscoveredControlOracle["kind"];
      coverage?: DiscoveredControlCoverageOutcome | TerminalDiscoveredControlCoverageOutcome;
    }
  | { kind: "finding"; band: InteractionOracleBand; oracle: DiscoveredControlOracle["kind"] };

const DECLARED_ARIA_STATES = new Set([
  "aria-checked",
  "aria-current",
  "aria-expanded",
  "aria-pressed",
  "aria-selected",
]);

const COMMAND_ROLES = new Set([
  "button",
  "checkbox",
  "menuitem",
  "menuitemcheckbox",
  "menuitemradio",
  "option",
  "radio",
  "switch",
  "tab",
  "treeitem",
]);

const TERMINAL_COVERAGE_KINDS = new Set<TerminalInteractionCoverageKind>([
  "unknown-terminal-state",
  "unknown-idempotency",
  "satisfied-terminal-state",
]);

const declaredAriaState = (control: DiscoveredControl): string | undefined =>
  control.stateSchema.find((state) => DECLARED_ARIA_STATES.has(state));

const booleanAriaState = (value: string | undefined): value is "true" | "false" =>
  value === "true" || value === "false";

function isTerminalCoverageKind(reason: OracleCoverageGapReason): reason is TerminalInteractionCoverageKind {
  return TERMINAL_COVERAGE_KINDS.has(reason as TerminalInteractionCoverageKind);
}

function canonicalObservedOrigin(value: string | undefined): string | undefined {
  if (value === undefined) return undefined;
  try {
    const parsed = new URL(value);
    if ((parsed.protocol !== "http:" && parsed.protocol !== "https:") || parsed.origin !== value) return undefined;
    return parsed.origin;
  } catch {
    return undefined;
  }
}

function sameOriginNavigationObserved(observation: DiscoveredControlOracleObservation): boolean {
  if (observation.navigated !== true) return false;
  const beforeOrigin = canonicalObservedOrigin(observation.beforeOrigin);
  const afterOrigin = canonicalObservedOrigin(observation.afterOrigin);
  return beforeOrigin !== undefined && beforeOrigin === afterOrigin;
}

function sameOriginNavigationUnproven(): DiscoveredControlOracleResult {
  return { kind: "unproven", oracle: "same-origin-link-navigation", coverage: coverageOutcome("unsupported-activation") };
}

function coverageOutcome(reason: InteractionCoverageKind): DiscoveredControlCoverageOutcome {
  const witnessId = `interaction-oracle:${reason}`;
  const witnessRefs: [{ id: string }] = [{ id: witnessId }];
  const obligationId = `activation:${reason}`;
  return {
    status: "coverage-incomplete",
    reason,
    phase: "activation",
    evidence: {
      kind: reason,
      phase: "activation",
      obligations: [{ id: obligationId, witnessRefs }],
    },
    witnessRefs,
  };
}

function terminalCoverageOutcome(
  reason: Exclude<TerminalInteractionCoverageKind, "satisfied-terminal-state">,
): TerminalDiscoveredControlCoverageOutcome {
  const witnessId = `interaction-oracle:${reason}`;
  const witnessRefs: [{ id: string }] = [{ id: witnessId }];
  const obligationId = `terminal:${reason}`;
  return {
    status: "coverage-incomplete",
    reason,
    phase: "terminal",
    evidence: {
      kind: reason,
      phase: "terminal",
      obligations: [{ id: obligationId, witnessRefs }],
    },
    witnessRefs,
  };
}

function satisfiedTerminalCoverageOutcome(input: {
  axis: string;
  value: "true" | "false";
}): SatisfiedTerminalEvidence {
  const witnessId = `interaction-oracle:satisfied-terminal-state:${input.axis}=${input.value}`;
  const witnessRefs: [{ id: string }] = [{ id: witnessId }];
  const obligationId = `terminal:satisfied:${input.axis}=${input.value}`;
  return {
    status: "terminal-evidence",
    reason: "satisfied-terminal-state",
    phase: "terminal",
    evidence: {
      kind: "satisfied-terminal-state",
      phase: "terminal",
      obligations: [{ id: obligationId, witnessRefs }],
    },
    witnessRefs,
  };
}

function idempotentTerminalOracle(
  assessment: Extract<IdempotentTerminalAssessment, { kind: "satisfied-terminal" }>,
): DiscoveredControlOracle {
  return {
    kind: "coverage-gap",
    reason: "satisfied-terminal-state",
    terminal: { axis: assessment.axis, value: assessment.value },
  };
}

function commandCoverage(control: DiscoveredControl): DiscoveredControlOracle {
  if (control.role === "link") {
    return { kind: "coverage-gap", reason: "unsupported-activation" };
  }
  if (COMMAND_ROLES.has(control.role)) {
    return { kind: "coverage-gap", reason: "unknown-command" };
  }
  return { kind: "coverage-gap", reason: "no-discoverable-contract" };
}

export function requiresDiscoveredControlActivation(oracle: DiscoveredControlOracle): boolean {
  return oracle.kind !== "coverage-gap";
}

export function deriveDiscoveredControlOracle(
  control: DiscoveredControl,
  options: { commandControls?: boolean; searchSubmission?: boolean } = {},
): DiscoveredControlOracle {
  if (control.disabled) return { kind: "coverage-gap", reason: "no-discoverable-contract" };

  const terminalAssessment = assessIdempotentTerminalState(control);
  if (terminalAssessment.kind === "satisfied-terminal") {
    return idempotentTerminalOracle(terminalAssessment);
  }
  if (terminalAssessment.kind === "unknown-terminal") {
    return { kind: "coverage-gap", reason: terminalAssessment.reason };
  }
  if (terminalAssessment.kind === "unknown-idempotency") {
    return { kind: "coverage-gap", reason: terminalAssessment.reason };
  }

  if (control.role === "button" && booleanAriaState(control.ariaPressed)) {
    return { kind: "aria-pressed-state-flip", band: "blocking" };
  }
  if (control.stateSchema.includes("aria-haspopup") || control.role === "dialog") {
    return { kind: "dialog-disclosure-focus", band: "advisory" };
  }
  if (control.landmark.startsWith("search:") && options.searchSubmission === true) {
    return { kind: "search-landmark-submit", band: "advisory" };
  }
  const state = declaredAriaState(control);
  if (state !== undefined) return { kind: "aria-declared-state", band: "advisory", state };
  if (
    control.validates
    && control.stateSchema.includes("required")
    && !control.stateSchema.includes("readonly")
    && !control.stateSchema.includes("form-novalidate")
    && !control.stateSchema.includes("formnovalidate")
  ) {
    return { kind: "required-form-validation", band: "blocking-conditional" };
  }
  if (options.commandControls === true) {
    if (control.role === "link" && control.sameOriginLink) {
      return { kind: "same-origin-link-navigation", band: "advisory" };
    }
    return commandCoverage(control);
  }
  return { kind: "coverage-gap", reason: "no-discoverable-contract" };
}

export function evaluateDiscoveredControlOracle(
  oracle: DiscoveredControlOracle,
  observation: DiscoveredControlOracleObservation,
): DiscoveredControlOracleResult {
  if (oracle.kind === "coverage-gap") {
    if (oracle.reason === "satisfied-terminal-state") {
      if (oracle.terminal === undefined) {
        return {
          kind: "unproven",
          oracle: oracle.kind,
          coverage: terminalCoverageOutcome("unknown-terminal-state"),
        };
      }
      return {
        kind: "pass",
        terminal: satisfiedTerminalCoverageOutcome(oracle.terminal),
      };
    }
    if (oracle.reason === "unknown-terminal-state" || oracle.reason === "unknown-idempotency") {
      return {
        kind: "unproven",
        oracle: oracle.kind,
        coverage: terminalCoverageOutcome(oracle.reason),
      };
    }
    return oracle.reason === "no-discoverable-contract"
      ? { kind: "unproven", oracle: oracle.kind }
      : {
          kind: "unproven",
          oracle: oracle.kind,
          coverage: isTerminalCoverageKind(oracle.reason)
            ? terminalCoverageOutcome(oracle.reason)
            : coverageOutcome(oracle.reason),
        };
  }
  if (oracle.kind === "aria-pressed-state-flip") {
    if (!booleanAriaState(observation.before) || !booleanAriaState(observation.after) || observation.before !== observation.after) {
      return { kind: "pass" };
    }
    return observation.reacted === true
      ? { kind: "finding", band: "blocking", oracle: oracle.kind }
      : { kind: "unproven", oracle: oracle.kind };
  }
  if (oracle.kind === "required-form-validation") {
    return observation.checkValidity === false
      && observation.invalidFired === true
      && observation.nativeSubmissionSuppressed === true
      ? { kind: "finding", band: "blocking-conditional", oracle: oracle.kind }
      : { kind: "pass" };
  }
  if (oracle.kind === "same-origin-link-navigation") {
    return sameOriginNavigationObserved(observation)
      ? { kind: "pass" }
      : sameOriginNavigationUnproven();
  }
  if (oracle.kind === "aria-declared-state" && observation.explicitStateContradiction === true) {
    return { kind: "finding", band: "blocking", oracle: oracle.kind };
  }
  if (oracle.kind === "dialog-disclosure-focus") {
    if (observation.explicitStateContradiction === true) {
      return { kind: "finding", band: "blocking", oracle: oracle.kind };
    }
    if (observation.ariaModal === true && observation.backgroundOperable === true) {
      return { kind: "finding", band: "blocking", oracle: oracle.kind };
    }
  }
  return { kind: "pass" };
}
