import { describe, it, expect, vi } from 'vitest'
import { createFormEngine } from '../src/canonical/composables/form/formEngine'
import type { FormRules } from '../src/canonical/composables/form/validators'

function make(model: Record<string, unknown>, rules: FormRules = {}) {
  const state = { ...model }
  const engine = createFormEngine({ getModel: () => state, getFormRules: () => rules })
  return { engine, state }
}

describe('createFormEngine — register + validate (submit)', () => {
  it('validates all registered fields with merged rules', async () => {
    const { engine } = make({ email: '', port: 8080 }, { email: { required: true } })
    engine.registerField('email', () => [])
    engine.registerField('port', () => [{ type: 'integer', min: 1, max: 65535 }])
    const res = await engine.validate()
    expect(res.valid).toBe(false)
    expect(res.errors.email).toBe('This field is required')
    expect(res.errors.port).toBe('')
  })

  it('valid=true when all pass', async () => {
    const { engine } = make({ email: 'a@b.com' }, { email: { required: true, type: 'email' } })
    engine.registerField('email', () => [])
    const res = await engine.validate()
    expect(res.valid).toBe(true)
    expect(res.errors.email).toBe('')
  })
})

describe('rule merging — table-level + field-level (field appended after)', () => {
  it('runs both; table-level failure wins when it is first', async () => {
    const { engine } = make({ name: '' }, { name: { required: true } })
    engine.registerField('name', () => [{ min: 3 }])
    const err = await engine.validateField('name')
    expect(err).toBe('This field is required') // table-level required runs first
  })
  it('field-level rule runs when table-level passes', async () => {
    const { engine } = make({ name: 'ab' }, { name: { required: true } })
    engine.registerField('name', () => [{ min: 3 }])
    const err = await engine.validateField('name')
    expect(err).toBe('Must be at least 3 characters')
  })
})

describe('validateField — trigger filtering', () => {
  it('blur skips a change-only rule (no-op keeps prior error)', async () => {
    const { engine } = make({ email: '' })
    engine.registerField('email', () => [{ required: true, trigger: 'change' }])
    const err = await engine.validateField('email', 'blur')
    expect(err).toBe('') // no blur rules → no-op, error stays empty
  })
  it('change runs a change rule', async () => {
    const { engine } = make({ email: '' })
    engine.registerField('email', () => [{ required: true, trigger: 'change' }])
    const err = await engine.validateField('email', 'change')
    expect(err).toBe('This field is required')
  })
  it('rule with no trigger defaults to change', async () => {
    const { engine } = make({ email: '' })
    engine.registerField('email', () => [{ required: true }])
    // blur first on a fresh field: a no-trigger rule defaults to 'change', so
    // blur finds no applicable rules → no-op → '' (no prior error to keep).
    expect(await engine.validateField('email', 'blur')).toBe('')
    // change: the default-trigger rule fires.
    expect(await engine.validateField('email', 'change')).toBe('This field is required')
  })
})

describe('subscribe — notified on error changes', () => {
  it('notifies listeners with the errors snapshot', async () => {
    const { engine } = make({ email: '' })
    engine.registerField('email', () => [{ required: true }])
    const listener = vi.fn()
    engine.subscribe(listener)
    await engine.validateField('email', 'change')
    expect(listener).toHaveBeenCalledWith({ email: 'This field is required' })
  })
  it('unsubscribe stops notifications', async () => {
    const { engine } = make({ email: '' })
    engine.registerField('email', () => [{ required: true }])
    const listener = vi.fn()
    const unsub = engine.subscribe(listener)
    unsub()
    await engine.validateField('email', 'change')
    expect(listener).not.toHaveBeenCalled()
  })
})

describe('resetFields + clearValidate', () => {
  it('resetFields restores initial model values and clears errors', async () => {
    const { engine, state } = make({ email: 'init@b.com' })
    engine.registerField('email', () => [{ required: true }])
    state.email = ''
    await engine.validate()
    expect(engine.getError('email')).toBe('This field is required')
    engine.resetFields()
    expect(state.email).toBe('init@b.com') // value restored from snapshot
    expect(engine.getError('email')).toBe('') // validation cleared
  })
  it('clearValidate(prop) clears one field error without touching values', async () => {
    const { engine, state } = make({ email: '', port: 0 })
    engine.registerField('email', () => [{ required: true }])
    engine.registerField('port', () => [{ type: 'integer', min: 1 }])
    await engine.validate()
    engine.clearValidate('email')
    expect(engine.getError('email')).toBe('')
    expect(engine.getError('port')).toBe('Must be at least 1')
    expect(state.email).toBe('') // value untouched
  })
})

describe('null-safe model (React/CE binding sets model asynchronously)', () => {
  it('createFormEngine does not throw when getModel() is undefined at creation', () => {
    // Under the React wrapper the `model` element property is set via useEffect
    // AFTER first mount, so Form.vue setup can create the engine with model=undefined.
    let model: Record<string, unknown> | undefined
    const engine = createFormEngine({ getModel: () => model, getFormRules: () => ({}) })
    engine.registerField('email', () => [{ required: true }])
    // model arrives later
    model = { email: '' }
    expect(engine.getFieldValue('email')).toBe('')
    return engine.validate().then((res) => {
      expect(res.valid).toBe(false)
      expect(res.errors.email).toBe('This field is required')
    })
  })
})

describe('getFieldValue + unregisterField', () => {
  it('getFieldValue reads live model', () => {
    const { engine, state } = make({ email: 'x' })
    expect(engine.getFieldValue('email')).toBe('x')
    state.email = 'y'
    expect(engine.getFieldValue('email')).toBe('y')
  })
  it('unregisterField drops a field from validate()', async () => {
    const { engine } = make({ email: '' })
    engine.registerField('email', () => [{ required: true }])
    engine.unregisterField('email')
    const res = await engine.validate()
    expect(res.valid).toBe(true)
    expect(res.errors.email).toBeUndefined()
  })
})
