"use strict";

const assert = require("node:assert");
const { test } = require("node:test");
const {
  attributeValueType,
  parseComponentMetadata,
  parseComponentTags,
  validateComponentTags,
} = require("../src/component-metadata");

const source = `component Button {
  props {
    // @required
    label = ""
    variant = "primary"
    disabled = false
    count = 0
    items = []
  }
  view {
    <button class="{variant === 'primary' ? 'brand' : variant === 'danger' ? 'danger' : ''}">{label}</button>
  }
}`;

test("extracts component prop types, required props, defaults, and options", () => {
  const component = parseComponentMetadata(source);

  assert.equal(component.name, "Button");
  assert.deepEqual(
    component.props.map(({ name, type, required, options }) => ({ name, type, required, options })),
    [
      { name: "label", type: "string", required: true, options: [] },
      { name: "variant", type: "string", required: false, options: ["danger", "primary"] },
      { name: "disabled", type: "boolean", required: false, options: [] },
      { name: "count", type: "number", required: false, options: [] },
      { name: "items", type: "array", required: false, options: [] },
    ],
  );
});

test("parses multiline component tags and their attribute locations", () => {
  const page = `<Button
    label="Save"
    disabled="{false}"
  />`;
  const [tag] = parseComponentTags(page);

  assert.equal(tag.name, "Button");
  assert.deepEqual(
    tag.attributes.map(({ name, value }) => ({ name, value })),
    [
      { name: "label", value: "Save" },
      { name: "disabled", value: "{false}" },
    ],
  );
});

test("reports missing, unknown, mismatched, and invalid-option component props", () => {
  const catalog = new Map([["Button", parseComponentMetadata(source)]]);
  const diagnostics = validateComponentTags(
    `<Button variant="quiet" count="many" extra="value" />`,
    catalog,
  );

  assert.deepEqual(
    diagnostics.map(({ code, severity }) => ({ code, severity })),
    [
      { code: "wrn-missing-component-prop", severity: "error" },
      { code: "wrn-component-prop-option", severity: "warning" },
      { code: "wrn-component-prop-type", severity: "error" },
      { code: "wrn-unknown-component-prop", severity: "warning" },
    ],
  );
});

test("recognizes literal attribute value types", () => {
  assert.equal(attributeValueType("{false}"), "boolean");
  assert.equal(attributeValueType("{42}"), "number");
  assert.equal(attributeValueType("{[1, 2]}"), "array");
  assert.equal(attributeValueType("plain"), "string");
});

test("extracts explicit prop types and resolves typed prop/state expressions", () => {
  const input = `component DatePicker {
    props {
      required: boolean = false
      value: string
    }
    state count: number = 0
    view { <DateInput required="{required}" value="{value}" count="{count}" /> }
  }`;
  const target = parseComponentMetadata(`component DateInput {
    props {
      required: boolean = false
      value: string
      count: number = 0
    }
    view { <input/> }
  }`);
  const own = parseComponentMetadata(input);
  assert.deepEqual(
    own.props.map(({ name, type, required }) => ({ name, type, required })),
    [
      { name: "required", type: "boolean", required: false },
      { name: "value", type: "string", required: true },
    ],
  );
  assert.deepEqual(validateComponentTags(input, new Map([["DateInput", target]])), []);
});

test("extracts declared events and validates component event bindings", () => {
  const target = parseComponentMetadata(`component PinInput {
    props {
      value = ""
      @event complete = function
      @event clear = function
    }
    view { <div></div> }
  }`);

  assert.deepEqual(target.events, ["complete", "clear"]);
  assert.deepEqual(
    validateComponentTags(
      `<PinInput @complete="save(event)" @missing="fail(event)" />`,
      new Map([["PinInput", target]]),
    ).map(({ code }) => code),
    ["wrn-unknown-component-event"],
  );
});

test("extracts v0.6 optional props, declared union options, and outputs", () => {
  const metadata = parseComponentMetadata(`component Dialog {
    outputs { confirm(payload: string) cancel() }
    props {
      title: string
      description?: string
      size: "small" | "large" = "small"
    }
    view { <div></div> }
  }`);
  assert.deepEqual(metadata.events, ["confirm", "cancel"]);
  assert.deepEqual(
    metadata.props.map(({ name, required, options }) => ({ name, required, options })),
    [
      { name: "title", required: true, options: [] },
      { name: "description", required: false, options: [] },
      { name: "size", required: false, options: ["small", "large"] },
    ],
  );
});

test("accepts unresolved dynamic expressions for typed component props", () => {
  const featureCard = parseComponentMetadata(`component FeatureCard {
    props {
      external: boolean = false
      disabled: boolean = false
      showArrow: boolean = true
    }
    view { <article></article> }
  }`);
  const source = `component FeatureGrid {
    props { items: unknown[] = [] }
    view {
      {#each items as item}
        <FeatureCard
          external='{item.external || false}'
          disabled='{item.disabled || false}'
          showArrow='{item.showArrow !== false}'
        />
      {/each}
    }
  }`;

  assert.equal(attributeValueType("{item.external || false}"), "unknown");
  assert.equal(attributeValueType("{item.showArrow !== false}"), "boolean");
  assert.deepEqual(validateComponentTags(source, new Map([["FeatureCard", featureCard]])), []);
});

test("continues to reject invalid literal boolean props", () => {
  const target = parseComponentMetadata(`component Toggle {
    props { enabled: boolean = false }
    view { <button></button> }
  }`);
  const diagnostics = validateComponentTags(
    `<Toggle enabled="definitely" />`,
    new Map([["Toggle", target]]),
  );
  assert.deepEqual(
    diagnostics.map(({ code }) => code),
    ["wrn-component-prop-type"],
  );
});

test("accepts unresolved dynamic expressions for declared string options", () => {
  const target = parseComponentMetadata(`component FeatureCard {
    props { target: "_blank" | "_self" = "_self" }
    view { <article></article> }
  }`);
  const dynamic = `<FeatureCard target='{item.target || ""}' />`;
  assert.deepEqual(validateComponentTags(dynamic, new Map([["FeatureCard", target]])), []);

  const invalidLiteral = validateComponentTags(
    `<FeatureCard target="popup" />`,
    new Map([["FeatureCard", target]]),
  );
  assert.deepEqual(
    invalidLiteral.map(({ code }) => code),
    ["wrn-component-prop-option"],
  );
});
