// shared helpers for chore-scheduler spec files

import { expect, type APIRequestContext, type Locator, type Page } from '@playwright/test'

// ─── Date Helpers ─────────────────────────────────────────────────────────────

export function todayISO(): string {
  const d = new Date()
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}

export function yesterdayISO(): string {
  const d = new Date()
  d.setDate(d.getDate() - 1)
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}

export function futureDateISO(daysAhead = 7): string {
  const d = new Date()
  d.setDate(d.getDate() + daysAhead)
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}

/** Today's weekday (0 = Sunday, 6 = Saturday) — matches DayConfig.day */
export function todayDayIndex(): number {
  return new Date().getDay()
}

/** A weekday guaranteed not to be today */
export function nonTodayDayIndex(): number {
  return (new Date().getDay() + 1) % 7
}

// ─── API Helpers ──────────────────────────────────────────────────────────────

export async function createChild(request: APIRequestContext, name: string): Promise<string> {
  const pre = await request.get('/api/child/list')
  for (const c of (await pre.json()).children ?? []) {
    if (c.name === name) await request.delete(`/api/child/${c.id}`)
  }
  await request.put('/api/child/add', { data: { name, age: 8 } })
  const list = await request.get('/api/child/list')
  return (
    (await list.json()).children?.find((c: { name: string; id: string }) => c.name === name)?.id ??
    ''
  )
}

export async function createChoreTask(request: APIRequestContext, name: string): Promise<string> {
  const pre = await request.get('/api/task/list')
  for (const t of (await pre.json()).tasks ?? []) {
    if (t.name === name) await request.delete(`/api/task/${t.id}`)
  }
  await request.put('/api/task/add', { data: { name, points: 5, type: 'chore' } })
  const list = await request.get('/api/task/list')
  return (
    (await list.json()).tasks?.find((t: { name: string; id: string }) => t.name === name)?.id ?? ''
  )
}

export async function assignChore(
  request: APIRequestContext,
  childId: string,
  choreIds: string[],
): Promise<void> {
  await request.put(`/api/child/${childId}/set-tasks`, {
    data: { task_ids: choreIds, type: 'chore' },
  })
}

export interface DaysScheduleOpts {
  hour?: number
  minute?: number
  hasDeadline?: boolean
  enabled?: boolean
}

export async function setDaysSchedule(
  request: APIRequestContext,
  childId: string,
  taskId: string,
  days: number[],
  opts: DaysScheduleOpts = {},
): Promise<void> {
  const { hour = 8, minute = 0, hasDeadline = true, enabled = true } = opts
  const day_configs = days.map((day) => ({ day, hour, minute }))
  await request.put(`/api/child/${childId}/task/${taskId}/schedule`, {
    data: {
      mode: 'days',
      day_configs,
      default_hour: hour,
      default_minute: minute,
      default_has_deadline: hasDeadline,
      enabled,
    },
  })
}

export interface IntervalScheduleOpts {
  hour?: number
  minute?: number
  hasDeadline?: boolean
  enabled?: boolean
}

export async function setIntervalSchedule(
  request: APIRequestContext,
  childId: string,
  taskId: string,
  intervalDays: number,
  anchorDate: string,
  opts: IntervalScheduleOpts = {},
): Promise<void> {
  const { hour = 8, minute = 0, hasDeadline = true, enabled = true } = opts
  await request.put(`/api/child/${childId}/task/${taskId}/schedule`, {
    data: {
      mode: 'interval',
      interval_days: intervalDays,
      anchor_date: anchorDate,
      interval_has_deadline: hasDeadline,
      interval_hour: hour,
      interval_minute: minute,
      enabled,
    },
  })
}

export async function deleteSchedule(
  request: APIRequestContext,
  childId: string,
  taskId: string,
): Promise<void> {
  // 404 is acceptable (no schedule existed)
  await request.delete(`/api/child/${childId}/task/${taskId}/schedule`)
}

// ─── UI Helpers ───────────────────────────────────────────────────────────────

/** Locator for the Chores section */
export function choreSection(page: Page): Locator {
  return page.locator('.child-list-container').filter({
    has: page.locator('h3', { hasText: 'Chores' }),
  })
}

/** Locator for a named chore card in the Chores section */
export function choreCard(page: Page, name: string): Locator {
  return choreSection(page)
    .locator('.item-card')
    .filter({
      has: page.locator('.item-name').getByText(name, { exact: true }),
    })
}

/** Click the chore card (select it), wait for ready state, then click Options */
export async function openChoreOptions(page: Page, card: Locator): Promise<void> {
  await card.click()
  await expect(card).toHaveClass(/item-ready/, { timeout: 3000 })
  await card.getByRole('button', { name: 'Options' }).click()
}

/** Open the schedule modal for a named chore card */
export async function openScheduleModal(page: Page, card: Locator): Promise<void> {
  await openChoreOptions(page, card)
  await page.getByRole('button', { name: 'Schedule' }).click()
  await expect(page.getByText('Schedule Chore')).toBeVisible()
}

/**
 * Set a TimePickerPopover to a specific time.
 * @param timePicker - the `.time-picker-popover` locator wrapping the popover
 */
export async function setTimePicker(
  page: Page,
  timePicker: Locator,
  hour12: number,
  minute: number,
  period: 'AM' | 'PM',
): Promise<void> {
  await timePicker.locator('.time-display').click()
  // Scope panel to this specific time picker to avoid strict-mode violations
  const panel = timePicker.locator('.popover-panel')
  await panel.waitFor()
  // Use exact regex patterns — getByRole name uses substring matching by default,
  // so "3" would also match "30" and "1" would match "10", "11", "12" etc.
  await panel.getByRole('button', { name: new RegExp(`^${period}$`) }).click()
  await panel.getByRole('button', { name: new RegExp(`^${hour12}$`) }).click()
  await panel
    .getByRole('button', { name: new RegExp(`^${String(minute).padStart(2, '0')}$`) })
    .click()
  // Close popover by clicking the modal title (outside popover bounds)
  await page.locator('.modal-title').click()
  await panel.waitFor({ state: 'hidden' })
}

/** Set the hidden date input and fire a change event */
export async function setDateInput(page: Page, isoDate: string): Promise<void> {
  await page.locator('input.date-input-hidden').evaluate((el: HTMLInputElement, val: string) => {
    el.value = val
    el.dispatchEvent(new Event('change', { bubbles: true }))
  }, isoDate)
}

/**
 * Navigate to the child view for a given child.
 * Parent-authenticated users are blocked from /child by the auth guard, so we
 * must clear the parentAuth localStorage key before navigating there.
 */
export async function goToChildView(page: Page, childId: string): Promise<void> {
  // Go to the app domain first so we can access its localStorage
  await page.goto(`/parent/${childId}`)
  // Clear the parent auth token — router will now allow /child/:id
  await page.evaluate(() => localStorage.removeItem('parentAuth'))
  // Navigate to child view and wait for the chore section to appear
  await page.goto(`/child/${childId}`)
  await page.locator('.child-list-container').first().waitFor({ state: 'visible', timeout: 10000 })
}

/**
 * Navigate (back) to the parent view from any page.
 * Restores the parentAuth localStorage key so the router allows /parent/:id.
 */
export async function goToParentView(page: Page, childId: string): Promise<void> {
  // Restore parent auth so the router guard allows /parent/:id
  await page.evaluate(() => {
    localStorage.setItem(
      'parentAuth',
      JSON.stringify({ expiresAt: Date.now() + 2 * 24 * 60 * 60 * 1000 }),
    )
  })
  await page.goto(`/parent/${childId}`)
  await page.locator('.child-list-container').first().waitFor({ state: 'visible', timeout: 10000 })
}
