// The invariant the whole map model rests on: a position without a level is not
// drawn on the default level.
//
// Nothing else catches this. Rewriting the composable left four components
// reading a property that no longer existed, which is `undefined` rather than a
// compile error - Vite built it happily and every marker would have been
// positioned at NaN percent. A build proves the code parses, not that a marker
// lands anywhere.

import { describe, it, expect, vi, beforeEach } from 'vitest'

const list = vi.fn()
vi.mock('../api', () => ({ mapLevelsApi: { list: (...args) => list(...args) } }))

const {
  state, loadMapConfig, reloadMapConfig, blueprintUrlFor, dimensionsFor,
  hasLevel, levelName, levelOptions, setCurrentLevel,
} = await import('./mapConfig')

// Two buildings, three levels, sized like the real before and after: the ground
// floor at 3300x2550 and a second floor at 3308x4000.
const PAYLOAD = {
  data: {
    data: {
      buildings: [
        {
          buildingid: 1, buildingname: 'Main', levels: [
            { levelid: 1, levelname: 'Ground floor', sortorder: 0, mapwidth: 3300,
              mapheight: 2550, blueprintlight: '/x/g-light.png',
              blueprintdark: '/x/g-dark.png', isdefault: true },
            { levelid: 2, levelname: 'Second floor', sortorder: 1, mapwidth: 3308,
              mapheight: 4000, blueprintlight: '/x/2-light.png',
              blueprintdark: null, isdefault: false },
          ],
        },
        {
          buildingid: 2, buildingname: 'Annex', levels: [
            { levelid: 3, levelname: 'Ground floor', sortorder: 0, mapwidth: 1200,
              mapheight: 900, blueprintlight: '/x/a-light.png',
              blueprintdark: '/x/a-dark.png', isdefault: false },
          ],
        },
      ],
      defaultlevelid: 1,
    },
  },
}

beforeEach(async () => {
  list.mockReset()
  list.mockResolvedValue(PAYLOAD)
  state.loaded = false
  state.currentlevelid = null
  await reloadMapConfig()
})

describe('loading', () => {
  it('indexes every level of every building and adopts the default', () => {
    expect(Object.keys(state.levels)).toEqual(['1', '2', '3'])
    expect(state.defaultlevelid).toBe(1)
    expect(state.currentlevelid).toBe(1)
  })

  it('fetches once across concurrent callers', async () => {
    list.mockClear()
    state.loaded = false
    await Promise.all([loadMapConfig(), loadMapConfig(), loadMapConfig()])
    expect(list).toHaveBeenCalledTimes(1)
  })

  it('survives an unreachable server without hanging every map on the page', async () => {
    list.mockReset()
    list.mockRejectedValue(new Error('network'))
    state.loaded = false
    await reloadMapConfig()
    expect(state.loaded).toBe(true)
  })
})

describe('dimensions belong to the level, not the site', () => {
  it('returns each level its own native size', () => {
    expect(dimensionsFor(1)).toEqual({ width: 3300, height: 2550 })
    expect(dimensionsFor(2)).toEqual({ width: 3308, height: 4000 })
    expect(dimensionsFor(3)).toEqual({ width: 1200, height: 900 })
  })

  it('does not report one level size for another', () => {
    // The bug this guards: a marker on level 2 measured against level 1's
    // height renders at 2550/4000 of the way down - plausible, and wrong.
    expect(dimensionsFor(2).height).not.toBe(dimensionsFor(1).height)
  })
})

describe('a position without a level is not drawn', () => {
  it('has no blueprint for a null level', () => {
    expect(blueprintUrlFor('light', null)).toBeNull()
    expect(hasLevel(null)).toBe(false)
  })

  it('has no blueprint for a level this instance does not know', () => {
    expect(blueprintUrlFor('light', 99)).toBeNull()
    expect(hasLevel(99)).toBe(false)
  })

  it('never substitutes the default level for a missing one', () => {
    const groundfloor = blueprintUrlFor('light', 1)
    expect(groundfloor).toContain('g-light.png')
    // The failure mode: returning the default blueprint for an unknown level.
    expect(blueprintUrlFor('light', null)).not.toBe(groundfloor)
    expect(blueprintUrlFor('light', 99)).not.toBe(groundfloor)
  })
})

describe('blueprints', () => {
  it('serves the theme asked for', () => {
    expect(blueprintUrlFor('light', 1)).toContain('g-light.png')
    expect(blueprintUrlFor('dark', 1)).toContain('g-dark.png')
  })

  it('falls back to the other theme rather than showing nothing', () => {
    // Level 2 has no dark blueprint. A hard-to-read floor plan beats no floor
    // plan, and a site that uploaded one image should still work in both themes.
    expect(blueprintUrlFor('dark', 2)).toContain('2-light.png')
  })
})

describe('naming and selection', () => {
  it('qualifies a level by building only when there is more than one', () => {
    // Both buildings have a 'Ground floor', so the name alone is ambiguous.
    expect(levelName(1)).toBe('Main / Ground floor')
    expect(levelName(3)).toBe('Annex / Ground floor')
  })

  it('offers every level in building then level order', () => {
    expect(levelOptions().map(option => option.levelid)).toEqual([1, 2, 3])
  })

  it('refuses to make an unknown level current', () => {
    setCurrentLevel(99)
    expect(state.currentlevelid).toBe(1)
    setCurrentLevel(2)
    expect(state.currentlevelid).toBe(2)
  })
})
