// Icons
import { aliases, mdi } from '@/iconsets/mdi'

// Utilities
import { computed, inject, unref } from 'vue'
import {
  defineComponent,
  genericComponent,
  mergeDeep,
  propsFactory,
} from '@/utils'

// Types
import type { InjectionKey, JSXComponent, PropType, Ref } from 'vue'
import ColorName from '@/types/colors'
import { getNextColor } from '@/utils/useColors'

import { makeComponentProps } from '@/composables/component'

export type IconValue =
  | string
  | (string | [path: string, opacity: number])[]
  | JSXComponent
export const IconValue = [
  String,
  Function,
  Object,
  Array,
] as PropType<IconValue>

export interface IconAliases {
  [name: string]: IconValue
}

export interface IconProps {
  tag: string
  icon?: IconValue
  disabled?: boolean
}

type IconComponent = JSXComponent<IconProps>

export interface IconSet {
  component: IconComponent
}

export type IconOptions = {
  defaultSet?: string
  aliases?: Partial<IconAliases>
  sets?: Record<string, IconSet>
}

type IconInstance = {
  component: IconComponent
  icon?: IconValue
}

export const IconSymbol: InjectionKey<Required<IconOptions>> =
  Symbol.for('untitled:icons')

export const makeIconProps = propsFactory(
  {
    icon: {
      type: IconValue,
    },
    // Could not remove this and use makeTagProps, types complained because it is not required
    tag: {
      type: String,
      required: true,
    },

    ...makeComponentProps(),
  },
  'icon'
)

export const UComponentIcon = genericComponent()({
  name: 'UComponentIcon',

  props: makeIconProps(),

  setup(props, { slots }) {
    return () => {
      const Icon = props.icon as JSXComponent
      return <props.tag>{props.icon ? <Icon /> : slots.default?.()}</props.tag>
    }
  },
})
export type VComponentIcon = InstanceType<typeof UComponentIcon>

export const USvgIcon = defineComponent({
  name: 'USvgIcon',

  inheritAttrs: false,

  props: makeIconProps(),

  setup(props, { attrs }) {
    const hover = computed(() => {
      return attrs.isHover
    })

    const active = computed(() => {
      return attrs.isActive
    })

    const hoverIconClasses = computed<string>(
      () => `hover:stroke-${getNextColor(attrs.color as ColorName)}`
    )

    const hoverButtonClasses = computed<string>(
      () => `stroke-${getNextColor(attrs.color as ColorName)}`
    )

    const classes = computed(() => ({
      [hoverIconClasses.value]: true,
      [hoverButtonClasses.value]:
        hover.value === true && active.value === false,
      [`stroke-${attrs.color} `]:
        hover.value === false || active.value === true,
    }))

    return () => {
      return (
        <props.tag {...attrs} style={null} class={props.class}>
          <svg
            class={['u-icon__svg', classes.value]}
            // eslint-disable-next-line @typescript-eslint/ban-ts-comment
            // @ts-ignore
            style={props.style}
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 24 24"
            role="img"
            aria-hidden="true"
            fill="none"
          >
            {Array.isArray(props.icon) ? (
              props.icon.map((path) =>
                Array.isArray(path) ? (
                  <path d={path[0] as string} fill-opacity={path[1]}></path>
                ) : (
                  <path
                    d={path as string}
                    stroke-width="2"
                    stroke-linecap="round"
                    stroke-linejoin="round"
                  ></path>
                )
              )
            ) : (
              <path
                d={props.icon as string}
                stroke-width="2"
                stroke-linecap="round"
                stroke-linejoin="round"
              ></path>
            )}
          </svg>
        </props.tag>
      )
    }
  },
})
export type USvgIcon = InstanceType<typeof USvgIcon>

export const ULigatureIcon = defineComponent({
  name: 'ULigatureIcon',

  props: makeIconProps(),

  setup(props) {
    return () => {
      return <props.tag>{props.icon}</props.tag>
    }
  },
})
export type ULigatureIcon = InstanceType<typeof ULigatureIcon>

export const UClassIcon = defineComponent({
  name: 'UClassIcon',

  props: makeIconProps(),

  setup(props) {
    return () => {
      return <props.tag class={props.icon}></props.tag>
    }
  },
})
export type UClassIcon = InstanceType<typeof UClassIcon>

export const defaultSets: Record<string, IconSet> = {
  svg: {
    component: USvgIcon,
  },
  class: {
    component: UClassIcon,
  },
}

// Composables
export function createIcons(options?: IconOptions) {
  return mergeDeep(
    {
      defaultSet: 'mdi',
      sets: {
        ...defaultSets,
        mdi,
      },
      aliases: {
        ...aliases,
        /* eslint-disable max-len */
        vuetify: [
          'M8.2241 14.2009L12 21L22 3H14.4459L8.2241 14.2009Z',
          [
            'M7.26303 12.4733L7.00113 12L2 3H12.5261C12.5261 3 12.5261 3 12.5261 3L7.26303 12.4733Z',
            0.6,
          ],
        ],
        'vuetify-outline':
          'svg:M7.26 12.47 12.53 3H2L7.26 12.47ZM14.45 3 8.22 14.2 12 21 22 3H14.45ZM18.6 5 12 16.88 10.51 14.2 15.62 5ZM7.26 8.35 5.4 5H9.13L7.26 8.35Z',
        /* eslint-enable max-len */
      },
    },
    options
  )
}

export const useIcon = (props: Ref<IconValue | undefined>) => {
  const icons = inject(IconSymbol)

  if (!icons) throw new Error('Missing Untitled Icons provide!')

  const iconData = computed<IconInstance>(() => {
    const iconAlias = unref(props)

    if (!iconAlias) return { component: UComponentIcon }

    let icon: IconValue | undefined = iconAlias

    if (typeof icon === 'string') {
      icon = icon.trim()

      icon = icon.startsWith('$')
        ? icons.aliases?.[icon.slice(1)]
        : icons.aliases?.[icon]
    }

    if (!icon) throw new Error(`Could not find aliased icon "${iconAlias}"`)

    if (Array.isArray(icon)) {
      return {
        component: USvgIcon,
        icon,
      }
    } else if (typeof icon !== 'string') {
      return {
        component: UComponentIcon,
        icon,
      }
    }

    const iconSetName = Object.keys(icons.sets).find(
      (setName) => typeof icon === 'string' && icon.startsWith(`${setName}:`)
    )

    const iconName = iconSetName ? icon.slice(iconSetName.length + 1) : icon
    const iconSet = icons.sets[iconSetName ?? icons.defaultSet]

    return {
      component: iconSet.component,
      icon: iconName,
    }
  })

  return { iconData }
}
