import { Page, expect } from "@playwright/test"
import { clickToReveal, clickToRevealWithReload, selectOption, comboboxByLabel, submitAndWait } from "../support/ui"

// Locators verified against Frontend/erp-system/app/dashboard/production/runs/page.tsx,
// .../runs/[id]/page.tsx, .../runs/[id]/StageDrawer.tsx, and .../runs/[id]/RunActions.tsx.
// Key DOM facts that shaped these locators:
//   - "Start Run" (the list page's dialog trigger, capital R) and "Start run" (the dialog's
//     submit button, lowercase r) both match a case-insensitive /start run/i once the dialog
//     is open (the trigger stays mounted behind it) - the submit click is scoped to
//     getByRole("dialog") to avoid a strict-mode double match.
//   - STAGE_STATUS_LABEL.InProgress is "In Progress" - the same text StageStatusLegend
//     always renders on the run detail page, so a run-status assertion of "In Progress"
//     collides with the legend. expectStatus() takes the first DOM match, which is always
//     the run-header badge (it renders before the legend section).
//   - AlertDialogContent's rejectForRework confirmation reuses "Reject for rework" as both
//     the trigger and the confirm button's label - first()/last() disambiguates, same as
//     cancelRun's "Cancel run" trigger/confirm pair.
//   - Every page here intermittently throws a real React hydration error (#418) that blanks a
//     random subset of Select triggers' placeholder text for that page's lifetime, without
//     affecting their sibling <FieldLabel> or role="combobox" attribute (support/ui.ts has the
//     full writeup). Triggers are located via comboboxByLabel() (label + role, no name lookup)
//     instead of getByRole("combobox", { name }); the scrap-reason Select has no adjacent
//     label, so it's found via its "Scrapped" sibling block instead.
//   - Every stage/run action button here fires an async POST that the UI only reflects once the
//     response lands (StageDrawer/RunActions' `submit()` wrapper) - submitAndWait() (support/ui.ts)
//     waits for that specific response instead of just the click event, so a test reading stock
//     right after clicking "Approve & receive" (etc.) doesn't race the backend commit.
export class ProductionRunListPage {
  constructor(private readonly page: Page) {}

  async goto() {
    await this.page.goto("/dashboard/production/runs")
  }

  async openStartRunDialog() {
    await clickToReveal(
      this.page.getByRole("button", { name: /start run/i }),
      this.page.getByRole("dialog")
    )
  }

  async startRun(opts: { template: string; targetQty: number; warehouse: string }) {
    await this.openStartRunDialog()
    const dialog = this.page.getByRole("dialog")
    await selectOption(this.page, comboboxByLabel(dialog, "Template"), opts.template)
    await dialog.locator("#target-qty").fill(String(opts.targetQty))
    await selectOption(this.page, comboboxByLabel(dialog, "Warehouse"), opts.warehouse)
    await submitAndWait(this.page, dialog.getByRole("button", { name: /^start run$/i }), "/production-runs")
  }
}

export class ProductionRunDetailPage {
  constructor(private readonly page: Page) {}

  async gotoById(runId: number) {
    await this.page.goto(`/dashboard/production/runs/${runId}`)
  }

  async expectStatus(status: RegExp | string) {
    await expect(this.page.getByText(status).first()).toBeVisible()
  }

  /** Opens the StageDrawer for a named stage node on the React Flow canvas. */
  async openStage(stageName: string) {
    await clickToReveal(
      this.page.getByText(stageName, { exact: true }),
      this.page.getByRole("button", { name: /save quantities/i })
    )
  }

  /** The StageDrawer is a modal Sheet - run-level actions (Return leftover, Cancel run) sit
   *  behind it and need it dismissed first. */
  async closeStageDrawer() {
    await this.page.keyboard.press("Escape")
  }

  async saveQuantities() {
    // updateStageQuantities is a PUT, unlike every other stage action.
    await submitAndWait(this.page, this.page.getByRole("button", { name: /save quantities/i }), "/quantities", "PUT")
  }

  async startStage() {
    await submitAndWait(this.page, this.page.getByRole("button", { name: /^start stage$/i }), "/start")
  }

  async completeStage(opts: { producedQty: number; scrappedQty?: number }) {
    await this.page.getByRole("spinbutton", { name: /produced/i }).first().fill(String(opts.producedQty))
    if (opts.scrappedQty) {
      await this.page.getByRole("spinbutton", { name: /scrapped/i }).first().fill(String(opts.scrappedQty))
      const scrapBlock = this.page.getByText("Scrapped", { exact: true }).locator("../..")
      await selectOption(this.page, scrapBlock.getByRole("combobox"), /.+/)
    }
    await submitAndWait(this.page, this.page.getByRole("button", { name: /complete stage/i }), "/complete")
  }

  async approveAndReceive() {
    await submitAndWait(this.page, this.page.getByRole("button", { name: /approve\s*&\s*receive/i }), "/approve")
  }

  async approveAndTransfer() {
    await submitAndWait(this.page, this.page.getByRole("button", { name: /approve\s*&\s*transfer/i }), "/approve")
  }

  async rejectForRework() {
    const button = this.page.getByRole("button", { name: /^reject for rework$/i })
    await clickToReveal(button.first(), this.page.getByRole("dialog"))
    await submitAndWait(this.page, button.last(), "/reject")
  }

  async openReturnLeftoverDialog() {
    await clickToRevealWithReload(
      this.page,
      this.page.getByRole("button", { name: /return leftover/i }),
      this.page.getByRole("dialog")
    )
  }

  async returnLeftover(opts: { material: string; qty: number; reason: string }) {
    await this.openReturnLeftoverDialog()
    const dialog = this.page.getByRole("dialog")
    await selectOption(this.page, comboboxByLabel(dialog, "Consumed material"), opts.material)
    await dialog.locator("#return-qty").fill(String(opts.qty))
    await selectOption(this.page, comboboxByLabel(dialog, "Reason"), opts.reason)
    await submitAndWait(this.page, dialog.getByRole("button", { name: /return to stock/i }), "/return-leftover")
  }

  async cancelRun(opts: { reason: string; note?: string }) {
    const cancelRunButton = this.page.getByRole("button", { name: /^cancel run$/i })
    const dialog = this.page.getByRole("dialog")
    await clickToRevealWithReload(this.page, cancelRunButton.first(), dialog)
    await selectOption(this.page, comboboxByLabel(dialog, "Reason"), opts.reason)
    if (opts.note) await dialog.locator("#cancel-note").fill(opts.note)
    await submitAndWait(this.page, cancelRunButton.last(), "/cancel")
  }
}
