export type ShortcutScope =
    | 'global'
    | 'posts'
    | 'postSearch'
    | 'tags'
    | 'settings'
    | 'wiki'
    | 'materials'

export type ShortcutActionId =
    | 'global.openShortcutHelp'
    | 'global.focusSearch'
    | 'navigation.posts'
    | 'navigation.tags'
    | 'navigation.materials'
    | 'navigation.wiki'
    | 'pagination.next'
    | 'pagination.previous'
    | 'settings.account'
    | 'settings.behavior'
    | 'settings.theme'
    | 'settings.keyboard'

export type KeyBinding = {
    key:    string
    ctrl?:  boolean
    shift?: boolean
    alt?:   boolean
    meta?:  boolean
}

export type ShortcutDefinition = {
    id:              ShortcutActionId
    label:           string
    scope:           ShortcutScope
    defaultBinding:  KeyBinding | null
}

export const SHORTCUT_SCOPE_LABELS: Record<ShortcutScope, string> = {
    global:     '全画面',
    posts:      '広場',
    postSearch: '広場検索',
    tags:       'タグ',
    settings:   '設定',
    wiki:       'Wiki',
    materials:  '素材',
}

export const SHORTCUT_DEFINITIONS: ShortcutDefinition[] = [
    {
	id:              'global.openShortcutHelp',
	label:           'ショートカット一覧を開く',
	scope:           'global',
	defaultBinding:  { key: '?', shift: true },
    },
    {
	id:              'global.focusSearch',
	label:           '検索欄へ移動',
	scope:           'global',
	defaultBinding:  { key: '/' },
    },
    {
	id:              'navigation.posts',
	label:           '広場へ移動',
	scope:           'global',
	defaultBinding:  { key: 'p' },
    },
    {
	id:              'navigation.tags',
	label:           'タグへ移動',
	scope:           'global',
	defaultBinding:  { key: 't' },
    },
    {
	id:              'navigation.materials',
	label:           '素材へ移動',
	scope:           'global',
	defaultBinding:  { key: 'm' },
    },
    {
	id:              'navigation.wiki',
	label:           'Wiki へ移動',
	scope:           'global',
	defaultBinding:  { key: 'w' },
    },
    {
	id:              'pagination.next',
	label:           '次のページへ移動',
	scope:           'global',
	defaultBinding:  { key: 'ArrowRight' },
    },
    {
	id:              'pagination.previous',
	label:           '前のページへ移動',
	scope:           'global',
	defaultBinding:  { key: 'ArrowLeft' },
    },
    {
	id:              'settings.account',
	label:           '設定: アカウント',
	scope:           'settings',
	defaultBinding:  { key: '1' },
    },
    {
	id:              'settings.behavior',
	label:           '設定: 動作',
	scope:           'settings',
	defaultBinding:  { key: '2' },
    },
    {
	id:              'settings.theme',
	label:           '設定: テーマ',
	scope:           'settings',
	defaultBinding:  { key: '3' },
    },
    {
	id:              'settings.keyboard',
	label:           '設定: キーボード',
	scope:           'settings',
	defaultBinding:  { key: '4' },
    },
]

export const SHORTCUT_DEFINITIONS_BY_ID =
    SHORTCUT_DEFINITIONS.reduce<Record<ShortcutActionId, ShortcutDefinition>> (
	(definitions, definition) => {
	    definitions[definition.id] = definition
	    return definitions
	},
	{
	    'global.openShortcutHelp': SHORTCUT_DEFINITIONS[0],
	    'global.focusSearch': SHORTCUT_DEFINITIONS[1],
	    'navigation.posts': SHORTCUT_DEFINITIONS[2],
	    'navigation.tags': SHORTCUT_DEFINITIONS[3],
	    'navigation.materials': SHORTCUT_DEFINITIONS[4],
	    'navigation.wiki': SHORTCUT_DEFINITIONS[5],
	    'pagination.next': SHORTCUT_DEFINITIONS[6],
	    'pagination.previous': SHORTCUT_DEFINITIONS[7],
	    'settings.account': SHORTCUT_DEFINITIONS[8],
	    'settings.behavior': SHORTCUT_DEFINITIONS[9],
	    'settings.theme': SHORTCUT_DEFINITIONS[10],
	    'settings.keyboard': SHORTCUT_DEFINITIONS[11],
	},
    )

export const DEFAULT_KEY_BINDINGS =
    SHORTCUT_DEFINITIONS.reduce<Record<ShortcutActionId, KeyBinding | null>> (
	(bindings, definition) => {
	    bindings[definition.id] = definition.defaultBinding
	    return bindings
	},
	{
	    'global.openShortcutHelp': null,
	    'global.focusSearch': null,
	    'navigation.posts': null,
	    'navigation.tags': null,
	    'navigation.materials': null,
	    'navigation.wiki': null,
	    'pagination.next': null,
	    'pagination.previous': null,
	    'settings.account': null,
	    'settings.behavior': null,
	    'settings.theme': null,
	    'settings.keyboard': null,
	},
    )

const normaliseKey = (key: string): string => {
  if (key === 'Spacebar')
    return ' '
  if (key.length === 1 && key !== ' ')
    return key.toLowerCase ()
  return key
}

const isModifierKey = (key: string): boolean =>
    key === 'Shift'
    || key === 'Control'
    || key === 'Alt'
    || key === 'Meta'

export const normaliseKeyBinding = (value: unknown): KeyBinding | null => {
  if (!(value) || typeof value !== 'object')
    return null

  const binding = value as Partial<KeyBinding>
  if (typeof binding.key !== 'string')
    return null

  const key = normaliseKey (binding.key)
  if (key.length === 0 || isModifierKey (key))
    return null

  return {
      key,
      ...(binding.ctrl ? { ctrl: true } : { }),
      ...(binding.shift ? { shift: true } : { }),
      ...(binding.alt ? { alt: true } : { }),
      ...(binding.meta ? { meta: true } : { }),
  }
}

export const serialiseKeyBinding = (binding: KeyBinding | null): string | null => {
  if (binding == null)
    return null

  const parts = [
      binding.ctrl ? 'Ctrl' : null,
      binding.shift ? 'Shift' : null,
      binding.alt ? 'Alt' : null,
      binding.meta ? 'Meta' : null,
      binding.key,
  ].filter ((part): part is string => part != null)

  return parts.join ('+')
}

const keyLabel = (key: string): string => {
  switch (key)
  {
  case ' ':
    return 'Space'

  case 'ArrowLeft':
    return 'ArrowLeft'

  case 'ArrowRight':
    return 'ArrowRight'

  case 'ArrowUp':
    return 'ArrowUp'

  case 'ArrowDown':
    return 'ArrowDown'

  case 'Escape':
    return 'Escape'

  case 'Backspace':
    return 'Backspace'

  case 'Delete':
    return 'Delete'

  default:
    return key.length === 1 ? key.toUpperCase () : key
  }
}

export const formatKeyBinding = (binding: KeyBinding | null): string => {
  if (binding == null)
    return '未割当'

  return [
      binding.ctrl ? 'Ctrl' : null,
      binding.shift ? 'Shift' : null,
      binding.alt ? 'Alt' : null,
      binding.meta ? 'Meta' : null,
      keyLabel (binding.key),
  ].filter ((part): part is string => part != null)
      .join (' + ')
}

export const keyBindingFromKeyboardEvent = (
    event: Pick<KeyboardEvent, 'key' | 'ctrlKey' | 'shiftKey' | 'altKey' | 'metaKey'>,
): KeyBinding | null => {
  const key = normaliseKey (event.key)
  if (key.length === 0 || isModifierKey (key))
    return null

  return {
      key,
      ...(event.ctrlKey ? { ctrl: true } : { }),
      ...(event.shiftKey ? { shift: true } : { }),
      ...(event.altKey ? { alt: true } : { }),
      ...(event.metaKey ? { meta: true } : { }),
  }
}

export const isEditableEventTarget = (target: EventTarget | null): boolean => {
  if (!(target instanceof Element))
    return false

  return target.closest (
      'input, textarea, select, [contenteditable="true"], [role="textbox"]',
  ) != null
}

export const getConflictingShortcutActionIds = (
    bindings: Record<ShortcutActionId, KeyBinding | null>,
): Set<ShortcutActionId> => {
  const grouped = new Map<string, ShortcutActionId[]> ()

  Object.entries (bindings).forEach (([actionId, binding]) => {
      const serialised = serialiseKeyBinding (binding)
      if (serialised == null)
	return

      const ids = grouped.get (serialised) ?? []
      ids.push (actionId as ShortcutActionId)
      grouped.set (serialised, ids)
  })

  return new Set (
      [...grouped.values ()]
	  .filter (actionIds => actionIds.length > 1)
	  .flat (),
  )
}

export const resolveShortcutScope = (pathname: string): ShortcutScope => {
  if (pathname.startsWith ('/posts/search'))
    return 'postSearch'
  if (pathname.startsWith ('/posts'))
    return 'posts'
  if (pathname.startsWith ('/tags'))
    return 'tags'
  if (pathname.startsWith ('/users/settings'))
    return 'settings'
  if (pathname.startsWith ('/wiki'))
    return 'wiki'
  if (pathname.startsWith ('/materials'))
    return 'materials'
  return 'global'
}

export const isSpaceBinding = (binding: KeyBinding | null): boolean =>
    binding?.key === ' '
