import { describe, it, expect } from 'vitest';
import {
    computeSignature,
    normalizeFilePath,
    extractStaticText,
    extractDistinctiveAttrs,
    type SignatureNode,
    type SignatureContext,
} from '../signature';

// Minimal node shape that matches what we extract from @vue/compiler-dom AST.
// In production we adapt the parser's ElementNode → SignatureNode.
const node = (overrides: Partial<SignatureNode> = {}): SignatureNode => ({
    tag: 'div',
    props: [],
    children: [],
    ...overrides,
});

const ctx = (overrides: Partial<SignatureContext> = {}): SignatureContext => ({
    filePath: 'app/resources/js/components/AppSidebar.vue',
    appRoot: 'app/',
    ancestorChain: ['AppSidebar'],
    sameTagOrdinal: 0,
    ...overrides,
});

describe('normalizeFilePath', () => {
    it('strips appRoot and .vue extension, uses forward slashes', () => {
        expect(
            normalizeFilePath('app/resources/js/components/A.vue', 'app/'),
        ).toBe('resources/js/components/A');
    });

    it('handles backslashes on Windows-style paths', () => {
        expect(
            normalizeFilePath('app\\resources\\js\\A.vue', 'app/'),
        ).toBe('resources/js/A');
    });
});

describe('extractStaticText', () => {
    it('returns first 24 chars of plain text child', () => {
        const n = node({ children: [{ type: 'text', content: 'Создать проект сейчас же' }] });
        expect(extractStaticText(n)).toBe('создать проект сейчас же');
    });

    it('truncates >24 chars', () => {
        const n = node({ children: [{ type: 'text', content: 'A'.repeat(50) }] });
        expect(extractStaticText(n)).toHaveLength(24);
    });

    it('ignores mustache interpolations', () => {
        const n = node({
            children: [
                { type: 'text', content: 'count is ' },
                { type: 'interpolation', content: '{{ count }}' },
            ],
        });
        expect(extractStaticText(n)).toBe('count is');
    });

    it('returns null when no static text', () => {
        const n = node({ children: [{ type: 'interpolation', content: '{{ x }}' }] });
        expect(extractStaticText(n)).toBeNull();
    });
});

describe('extractDistinctiveAttrs', () => {
    it('extracts allowlisted static attrs sorted by name', () => {
        const n = node({
            tag: 'v-btn',
            props: [
                { type: 'attribute', name: 'icon', value: 'plus' },
                { type: 'attribute', name: 'role', value: 'button' },
                { type: 'attribute', name: 'class', value: 'mr-2' },
            ],
        });
        expect(extractDistinctiveAttrs(n)).toBe('icon=plus,role=button');
    });

    it('ignores v-bind / : attrs (dynamic)', () => {
        const n = node({
            props: [
                { type: 'directive', name: 'bind', arg: 'icon', exp: 'someVar' },
                { type: 'attribute', name: 'id', value: 'static-id' },
            ],
        });
        expect(extractDistinctiveAttrs(n)).toBe('id=static-id');
    });

    it('returns empty string if no allowlisted attrs', () => {
        const n = node({ props: [{ type: 'attribute', name: 'class', value: 'foo' }] });
        expect(extractDistinctiveAttrs(n)).toBe('');
    });

    it('escape-hatch: data-dev-name takes precedence', () => {
        const n = node({
            tag: 'v-btn',
            props: [
                { type: 'attribute', name: 'icon', value: 'plus' },
                { type: 'attribute', name: 'data-dev-name', value: 'sidebar.create-btn' },
            ],
        });
        expect(extractDistinctiveAttrs(n)).toContain('data-dev-name=sidebar.create-btn');
    });
});

describe('computeSignature', () => {
    it('canonical signature for a plain element', () => {
        const n = node({
            tag: 'v-btn',
            props: [{ type: 'attribute', name: 'icon', value: 'plus' }],
            children: [{ type: 'text', content: 'Создать' }],
        });
        expect(computeSignature(n, ctx({ ancestorChain: ['AppSidebar', 'nav', 'v-list-item'] }))).toBe(
            'resources/js/components/AppSidebar::AppSidebar>nav>v-list-item::v-btn[icon=plus]::создать::0',
        );
    });

    it('identical templates produce identical signatures', () => {
        const a = node({ tag: 'span', children: [{ type: 'text', content: 'hi' }] });
        const b = node({ tag: 'span', children: [{ type: 'text', content: 'hi' }] });
        expect(computeSignature(a, ctx())).toBe(computeSignature(b, ctx()));
    });

    it('different ordinalAmongSameTag yields different signatures', () => {
        const a = node({ tag: 'v-btn' });
        const s0 = computeSignature(a, ctx({ sameTagOrdinal: 0 }));
        const s1 = computeSignature(a, ctx({ sameTagOrdinal: 1 }));
        expect(s0).not.toBe(s1);
    });

    it('data-dev-name overrides structural pieces', () => {
        const a = node({
            tag: 'v-btn',
            props: [{ type: 'attribute', name: 'data-dev-name', value: 'sidebar.create' }],
        });
        const sigA = computeSignature(a, ctx({ sameTagOrdinal: 0 }));
        // Same name, different ordinal — signature MUST be identical (escape hatch)
        const sigB = computeSignature(a, ctx({ sameTagOrdinal: 7 }));
        expect(sigA).toBe(sigB);
    });
});
