<script setup lang="ts">
import { computed, useAttrs, type Component } from 'vue';
import * as lucideIcons from 'lucide-vue-next';
import type { UiIconProps, UiIconSize } from './types';

defineOptions({
  name: 'UiIcon',
  inheritAttrs: false,
});

const props = withDefaults(defineProps<UiIconProps>(), {
  size: 'l',
  strokeWidth: 2,
});

/**
 * Pixel values for the named icon-size tokens.
 */
const NAMED_SIZE_PX: Record<UiIconSize, number> = {
  xs: 12,
  s: 16,
  m: 20,
  l: 24,
  xl: 32,
};

const resolvedSize = computed<number | string>(() => {
  const raw = props.size;
  if (typeof raw === 'string' && raw in NAMED_SIZE_PX) {
    return NAMED_SIZE_PX[raw as UiIconSize];
  }
  return raw as number | string;
});

const attrs = useAttrs();

// Type-safe lookup for Lucide icons
const iconLookup = lucideIcons as unknown as Record<string, Component>;

/**
 * Convert kebab-case or lowercase to PascalCase for Lucide icon lookup.
 * Examples: "circle-help" -> "CircleHelp", "star" -> "Star", "ChevronDown" -> "ChevronDown"
 * @param str
 */
function toPascalCase(str: string): string {
  // If already PascalCase (starts with uppercase, no hyphens), return as-is
  if (!str.includes('-') && str.charAt(0) === str.charAt(0).toUpperCase()) {
    return str;
  }
  return str
    .split('-')
    .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
    .join('');
}

const iconComponent = computed<Component | null>(() => {
  const pascalName = toPascalCase(props.name);
  return iconLookup[pascalName] ?? null;
});

// Compute the role: prefer attrs.role, then default based on ariaLabel
const computedRole = computed(() => {
  if (attrs.role) return attrs.role as string;
  return props.ariaLabel ? 'img' : undefined;
});

// Filter out role from attrs since we handle it explicitly
const filteredAttrs = computed(() => {
  const rest = { ...attrs };
  delete rest.role;
  return rest;
});
</script>

<template>
  <component
    :is="iconComponent"
    v-if="iconComponent"
    data-slot="icon"
    :size="resolvedSize"
    :stroke-width="props.strokeWidth"
    :role="computedRole"
    :aria-label="props.ariaLabel"
    :aria-hidden="props.ariaLabel ? undefined : 'true'"
    v-bind="filteredAttrs"
  />
</template>
