@page "/design/templates"
@using ScadaLink.Security
@using ScadaLink.Commons.Entities.Templates
@using ScadaLink.Commons.Interfaces.Repositories
@using ScadaLink.TemplateEngine
@using ScadaLink.TemplateEngine.Services
@attribute [Authorize(Policy = AuthorizationPolicies.RequireDesign)]
@inject ITemplateEngineRepository TemplateEngineRepository
@inject TemplateService TemplateService
@inject TemplateFolderService TemplateFolderService
@inject AuthenticationStateProvider AuthStateProvider
@inject NavigationManager NavigationManager
@inject IDialogService Dialog

<div class="container-fluid mt-3">
    <ToastNotification @ref="_toast" />

    <RenameFolderDialog @bind-IsVisible="_showRenameFolderDialog"
                        FolderId="_renameFolderId"
                        InitialName="@_renameFolderInitialName"
                        ErrorMessage="@_renameFolderError"
                        OnSubmit="SubmitRenameFolder" />

    <MoveTemplateDialog @bind-IsVisible="_showMoveTemplateDialog"
                        TemplateId="_moveTemplateId"
                        TemplateName="@_moveTemplateName"
                        FolderOptions="EnumerateFolderOptions()"
                        ErrorMessage="@_moveTemplateError"
                        OnSubmit="SubmitMoveTemplate" />

    <MoveFolderDialog @bind-IsVisible="_showMoveFolderDialog"
                      FolderId="_moveFolderId"
                      FolderName="@_moveFolderName"
                      FolderOptions="EnumerateFolderOptionsExcluding(_moveFolderId)"
                      ErrorMessage="@_moveFolderError"
                      OnSubmit="SubmitMoveFolder" />

    <ComposeIntoDialog @bind-IsVisible="_showComposeDialog"
                       SourceTemplateId="_composeSourceId"
                       SourceName="@_composeSourceName"
                       ParentOptions="EnumerateComposableParents(_composeSourceId)"
                       ErrorMessage="@_composeError"
                       OnSubmit="SubmitCompose" />

    @if (_loading)
    {
        <LoadingSpinner IsLoading="true" />
    }
    else if (_errorMessage != null)
    {
        <div class="alert alert-danger">@_errorMessage</div>
    }
    else
    {
        <div class="d-flex justify-content-between align-items-center mb-3">
            <h4 class="mb-0">Templates</h4>
            <div class="d-flex gap-2">
                <div class="dropdown">
                    <button class="btn btn-outline-secondary btn-sm dropdown-toggle"
                            data-bs-toggle="dropdown" aria-expanded="false">
                        Bulk actions
                    </button>
                    <ul class="dropdown-menu dropdown-menu-end">
                        <li>
                            <button class="dropdown-item" @onclick="() => _tree.ExpandAll()">Expand all folders</button>
                        </li>
                        <li>
                            <button class="dropdown-item" @onclick="() => _tree.CollapseAll()">Collapse all folders</button>
                        </li>
                    </ul>
                </div>
                <button class="btn btn-outline-secondary btn-sm"
                        title="New folder at root"
                        @onclick="() => OpenNewFolderDialog(null)">+ Folder</button>
                <button class="btn btn-primary btn-sm"
                        title="New template at root"
                        @onclick='() => NavigationManager.NavigateTo("/design/templates/create")'>+ Template</button>
            </div>
        </div>

        <div style="max-height: calc(100vh - 160px); overflow-y: auto; padding: 4px;">
            <TreeView @ref="_tree" TItem="TmplNode" Items="_treeRoots"
                      ChildrenSelector="n => n.Children"
                      HasChildrenSelector="n => n.Children.Count > 0"
                      KeySelector="n => (object)n.Key"
                      StorageKey="templates-tree">
                <NodeContent Context="node">
                    @RenderNodeLabel(node)
                </NodeContent>
                <ContextMenu Context="node">
                    @RenderNodeContextMenu(node)
                </ContextMenu>
                <EmptyContent>
                    <span class="text-muted fst-italic">No templates yet. Use the buttons above to create a folder or template.</span>
                </EmptyContent>
            </TreeView>
        </div>
    }
</div>

@code {
    // CentralUI-024: delegates to the shared helper so the claim type stays
    // resolved through JwtTokenService rather than a duplicated magic string.
    private Task<string> GetCurrentUserAsync()
        => AuthStateProvider.GetCurrentUsernameAsync();

    private List<Template> _templates = new();
    private List<TemplateFolder> _folders = new();

    private bool _loading = true;
    private string? _errorMessage;

    private ToastNotification _toast = default!;

    protected override async Task OnInitializedAsync()
    {
        await LoadTemplatesAsync();
    }

    private async Task LoadTemplatesAsync()
    {
        _loading = true;
        try
        {
            _templates = (await TemplateEngineRepository.GetAllTemplatesAsync()).ToList();
            _folders = (await TemplateEngineRepository.GetAllFoldersAsync()).ToList();
            BuildTemplateTree();
        }
        catch (Exception ex)
        {
            _errorMessage = $"Failed to load templates: {ex.Message}";
        }
        _loading = false;
    }

    private enum TmplNodeKind { Folder, Template, Composition }

    private record TmplNode(
        string Key,
        TmplNodeKind Kind,
        int EntityId,
        string Label,
        int? ParentFolderId,
        int? OwnerTemplateId,
        Template? Template,
        TemplateComposition? Composition,
        List<TmplNode> Children);

    private List<TmplNode> _treeRoots = new();

    private void BuildTemplateTree()
    {
        // 1. Folder nodes keyed by id
        var folderNodes = _folders.ToDictionary(
            f => f.Id,
            f => new TmplNode(
                Key: $"f:{f.Id}",
                Kind: TmplNodeKind.Folder,
                EntityId: f.Id,
                Label: f.Name,
                ParentFolderId: f.ParentFolderId,
                OwnerTemplateId: null,
                Template: null,
                Composition: null,
                Children: new List<TmplNode>()));

        // 2. Attach folder nodes by ParentFolderId
        var roots = new List<TmplNode>();
        foreach (var f in _folders.OrderBy(f => f.Name, StringComparer.OrdinalIgnoreCase))
        {
            var node = folderNodes[f.Id];
            if (f.ParentFolderId is int pid && folderNodes.TryGetValue(pid, out var parent))
                parent.Children.Add(node);
            else
                roots.Add(node);
        }

        // 3. Template nodes with composition leaves. Derived templates are
        // slot-owned and reached via their parent's composition leaf — never
        // shown as standalone tree nodes. Composition leaves recurse so a
        // composite slot (e.g. Pump composed with TempSensor) reveals its own
        // child slots when expanded.
        var templatesById = _templates.ToDictionary(t => t.Id);
        foreach (var t in _templates.Where(t => !t.IsDerived).OrderBy(t => t.Name, StringComparer.OrdinalIgnoreCase))
        {
            var compChildren = BuildCompositionLeaves(t, templatesById);

            var tNode = new TmplNode(
                Key: $"t:{t.Id}",
                Kind: TmplNodeKind.Template,
                EntityId: t.Id,
                Label: t.Name,
                ParentFolderId: t.FolderId,
                OwnerTemplateId: null,
                Template: t,
                Composition: null,
                Children: compChildren);

            if (t.FolderId is int fid && folderNodes.TryGetValue(fid, out var parentFolder))
                parentFolder.Children.Add(tNode);
            else
                roots.Add(tNode);
        }

        // 4. Sort each level: folders before templates, alphabetical
        SortChildren(roots);
        foreach (var node in folderNodes.Values)
            SortChildren(node.Children);

        _treeRoots = roots;
    }

    // Recursive: each composition leaf's children are the composed-template's
    // own composition leaves. Cascaded derived templates carry their slot
    // compositions, so walking ComposedTemplateId surfaces the full nested
    // structure.
    private static List<TmplNode> BuildCompositionLeaves(Template owner, IReadOnlyDictionary<int, Template> templatesById)
    {
        var result = new List<TmplNode>();
        foreach (var c in owner.Compositions.OrderBy(c => c.InstanceName, StringComparer.OrdinalIgnoreCase))
        {
            var nestedChildren = templatesById.TryGetValue(c.ComposedTemplateId, out var composed)
                ? BuildCompositionLeaves(composed, templatesById)
                : new List<TmplNode>();

            result.Add(new TmplNode(
                Key: $"c:{c.Id}",
                Kind: TmplNodeKind.Composition,
                EntityId: c.Id,
                Label: c.InstanceName,
                ParentFolderId: null,
                OwnerTemplateId: owner.Id,
                Template: null,
                Composition: c,
                Children: nestedChildren));
        }
        return result;
    }

    private static void SortChildren(List<TmplNode> children)
    {
        children.Sort((a, b) =>
        {
            var kindOrder = (int)a.Kind - (int)b.Kind;
            if (kindOrder != 0) return kindOrder;
            return string.Compare(a.Label, b.Label, StringComparison.OrdinalIgnoreCase);
        });
    }

    private TreeView<TmplNode> _tree = default!;

    private void OpenTemplate(int templateId) =>
        NavigationManager.NavigateTo($"/design/templates/{templateId}");

    private RenderFragment RenderNodeLabel(TmplNode node) => __builder =>
    {
        switch (node.Kind)
        {
            case TmplNodeKind.Folder:
                var folderOpen = _tree?.IsExpanded(node.Key) ?? false;
                <span class="tv-glyph"><i class="bi @(folderOpen ? "bi-folder2-open" : "bi-folder")"></i></span>
                <span class="tv-label @(node.Children.Count > 0 ? "fw-semibold" : "")"
                      title="@node.Label">@node.Label</span>
                @if (node.Children.Count > 0)
                {
                    <span class="tv-meta">
                        <span class="badge rounded-pill bg-secondary-subtle text-secondary-emphasis">@node.Children.Count</span>
                    </span>
                }
                break;

            case TmplNodeKind.Template:
                <span class="tv-glyph"><i class="bi bi-file-earmark-text"></i></span>
                <span class="tv-label @(node.Children.Count > 0 ? "fw-semibold" : "")"
                      title="@node.Label"
                      @ondblclick="() => OpenTemplate(node.EntityId)">@node.Label</span>
                break;

            case TmplNodeKind.Composition:
                <span class="tv-glyph"><i class="bi bi-arrow-return-right"></i></span>
                <span class="tv-label" title="@node.Label"
                      @ondblclick="() => OpenTemplate(node.Composition!.ComposedTemplateId)">@node.Label</span>
                break;
        }
    };

    private RenderFragment RenderNodeContextMenu(TmplNode node) => __builder =>
    {
        switch (node.Kind)
        {
            case TmplNodeKind.Folder:
                <button class="dropdown-item" @onclick="() => OpenNewFolderDialog(node.EntityId)">New Folder</button>
                <button class="dropdown-item" @onclick='() => NavigationManager.NavigateTo($"/design/templates/create?folderId={node.EntityId}")'>New Template</button>
                <button class="dropdown-item" @onclick="() => OpenRenameFolderDialog(node.EntityId, node.Label)">Rename</button>
                <button class="dropdown-item" @onclick="() => OpenMoveFolderDialog(node.EntityId, node.Label)">Move to Folder…</button>
                <div class="dropdown-divider"></div>
                <button class="dropdown-item text-danger" @onclick="() => DeleteFolder(node.EntityId, node.Label)">Delete</button>
                break;

            case TmplNodeKind.Template:
                <button class="dropdown-item" @onclick='() => NavigationManager.NavigateTo($"/design/templates/create?parentId={node.EntityId}")'>New Inheriting Template</button>
                <button class="dropdown-item" @onclick="() => OpenComposeDialog(node.Template!)">Compose into…</button>
                <button class="dropdown-item" @onclick="() => OpenMoveTemplateDialog(node.EntityId, node.Label)">Move to Folder…</button>
                <div class="dropdown-divider"></div>
                <button class="dropdown-item text-danger" @onclick="() => DeleteTemplate(node.Template!)">Delete</button>
                break;

            case TmplNodeKind.Composition:
                <button class="dropdown-item" @onclick='() => NavigationManager.NavigateTo($"/design/templates/{node.Composition!.ComposedTemplateId}")'>Open composed template</button>
                <button class="dropdown-item" @onclick="() => RenameComposition(node.Composition!)">Rename…</button>
                <div class="dropdown-divider"></div>
                <button class="dropdown-item text-danger" @onclick="() => DeleteComposition(node.Composition!)">Delete</button>
                break;
        }
    };

    // New-folder dialog: replaced the dedicated <NewFolderDialog> component with
    // IDialogService.PromptAsync. Validation failures surface via toast instead of
    // inline error text — the prompt UI doesn't have a slot for an error message.
    private async Task OpenNewFolderDialog(int? parentFolderId)
    {
        var name = await Dialog.PromptAsync("New folder", "Folder name", placeholder: "Folder name");
        if (string.IsNullOrWhiteSpace(name)) return;

        var user = await GetCurrentUserAsync();
        var result = await TemplateFolderService.CreateFolderAsync(name.Trim(), parentFolderId, user);
        if (result.IsSuccess)
        {
            _toast.ShowSuccess($"Folder '{result.Value.Name}' created.");
            await LoadTemplatesAsync();
        }
        else
        {
            _toast.ShowError(result.Error);
        }
    }

    // Move-template dialog state
    private bool _showMoveTemplateDialog;
    private int _moveTemplateId;
    private string _moveTemplateName = string.Empty;
    private string? _moveTemplateError;

    private void OpenMoveTemplateDialog(int templateId, string label)
    {
        _moveTemplateId = templateId;
        _moveTemplateName = label;
        _moveTemplateError = null;
        _showMoveTemplateDialog = true;
    }

    private async Task SubmitMoveTemplate((int TemplateId, int? NewFolderId) req)
    {
        _moveTemplateError = null;
        var user = await GetCurrentUserAsync();
        var result = await TemplateService.MoveTemplateAsync(req.TemplateId, req.NewFolderId, user);
        if (result.IsSuccess)
        {
            _showMoveTemplateDialog = false;
            _toast.ShowSuccess($"Template '{_moveTemplateName}' moved.");
            await LoadTemplatesAsync();
        }
        else
        {
            _moveTemplateError = result.Error;
        }
    }

    // Flat list of folders with indentation labels, for the picker.
    private IEnumerable<(int? Id, string Label)> EnumerateFolderOptions()
    {
        yield return (null, "(Root)");
        foreach (var f in WalkFolderHierarchy(_folders.Where(f => f.ParentFolderId == null), 0, excludeFolderId: null))
            yield return f;
    }

    // Same as EnumerateFolderOptions, but prunes the given folder and all its descendants
    // so the move dialog can't surface an obvious cycle target (server still validates).
    private IEnumerable<(int? Id, string Label)> EnumerateFolderOptionsExcluding(int excludeFolderId)
    {
        yield return (null, "(Root)");
        foreach (var f in WalkFolderHierarchy(_folders.Where(f => f.ParentFolderId == null), 0, excludeFolderId))
            yield return f;
    }

    private IEnumerable<(int? Id, string Label)> WalkFolderHierarchy(IEnumerable<TemplateFolder> level, int depth, int? excludeFolderId)
    {
        foreach (var f in level.OrderBy(f => f.Name, StringComparer.OrdinalIgnoreCase))
        {
            if (excludeFolderId.HasValue && f.Id == excludeFolderId.Value) continue;
            yield return ((int?)f.Id, new string(' ', depth * 2) + f.Name);
            foreach (var sub in WalkFolderHierarchy(_folders.Where(c => c.ParentFolderId == f.Id), depth + 1, excludeFolderId))
                yield return sub;
        }
    }

    // Move-folder dialog state
    private bool _showMoveFolderDialog;
    private int _moveFolderId;
    private string _moveFolderName = string.Empty;
    private string? _moveFolderError;

    private void OpenMoveFolderDialog(int folderId, string label)
    {
        _moveFolderId = folderId;
        _moveFolderName = label;
        _moveFolderError = null;
        _showMoveFolderDialog = true;
    }

    private async Task SubmitMoveFolder((int FolderId, int? NewParentId) req)
    {
        _moveFolderError = null;
        var user = await GetCurrentUserAsync();
        var result = await TemplateFolderService.MoveFolderAsync(req.FolderId, req.NewParentId, user);
        if (result.IsSuccess)
        {
            _showMoveFolderDialog = false;
            _toast.ShowSuccess($"Folder '{_moveFolderName}' moved.");
            await LoadTemplatesAsync();
        }
        else
        {
            _moveFolderError = result.Error;
        }
    }

    // Rename folder dialog state
    private bool _showRenameFolderDialog;
    private int _renameFolderId;
    private string _renameFolderInitialName = string.Empty;
    private string? _renameFolderError;

    private void OpenRenameFolderDialog(int folderId, string currentName)
    {
        _renameFolderId = folderId;
        _renameFolderInitialName = currentName;
        _renameFolderError = null;
        _showRenameFolderDialog = true;
    }

    private async Task SubmitRenameFolder((int FolderId, string NewName) req)
    {
        _renameFolderError = null;
        var user = await GetCurrentUserAsync();
        var result = await TemplateFolderService.RenameFolderAsync(req.FolderId, req.NewName, user);
        if (result.IsSuccess)
        {
            _showRenameFolderDialog = false;
            _toast.ShowSuccess("Folder renamed.");
            await LoadTemplatesAsync();
        }
        else
        {
            _renameFolderError = result.Error;
        }
    }

    private async Task DeleteFolder(int folderId, string label)
    {
        var confirmed = await Dialog.ConfirmAsync("Delete Folder", $"Delete folder '{label}'?", danger: true);
        if (!confirmed) return;

        var user = await GetCurrentUserAsync();
        var result = await TemplateFolderService.DeleteFolderAsync(folderId, user);
        if (result.IsSuccess)
        {
            _toast.ShowSuccess($"Folder '{label}' deleted.");
            await LoadTemplatesAsync();
        }
        else
        {
            _toast.ShowError(result.Error);
        }
    }

    private async Task DeleteTemplate(Template template)
    {
        var confirmed = await Dialog.ConfirmAsync(
            "Delete Template",
            $"Delete template '{template.Name}'? This will fail if instances or child templates reference it.",
            danger: true);
        if (!confirmed) return;

        try
        {
            var user = await GetCurrentUserAsync();
            var result = await TemplateService.DeleteTemplateAsync(template.Id, user);
            if (result.IsSuccess)
            {
                _toast.ShowSuccess($"Template '{template.Name}' deleted.");
                await LoadTemplatesAsync();
            }
            else
            {
                _toast.ShowError(result.Error);
            }
        }
        catch (Exception ex)
        {
            _toast.ShowError($"Delete failed: {ex.Message}");
        }
    }

    // ---- Compose-into dialog ----
    private bool _showComposeDialog;
    private int _composeSourceId;
    private string _composeSourceName = string.Empty;
    private string? _composeError;

    private void OpenComposeDialog(Template source)
    {
        _composeSourceId = source.Id;
        _composeSourceName = source.Name;
        _composeError = null;
        _showComposeDialog = true;
    }

    // Possible parents for a compose: every non-derived template except the source itself.
    // Server still validates cycles + collisions; the picker just trims obvious bad choices.
    private IEnumerable<(int Id, string Label)> EnumerateComposableParents(int sourceId)
    {
        return _templates
            .Where(t => !t.IsDerived && t.Id != sourceId)
            .OrderBy(t => t.Name, StringComparer.OrdinalIgnoreCase)
            .Select(t => (t.Id, t.Name));
    }

    private async Task SubmitCompose((int SourceTemplateId, int ParentTemplateId, string SlotName) req)
    {
        _composeError = null;
        var user = await GetCurrentUserAsync();
        var result = await TemplateService.AddCompositionAsync(req.ParentTemplateId, req.SourceTemplateId, req.SlotName, user);
        if (result.IsSuccess)
        {
            _showComposeDialog = false;
            _toast.ShowSuccess($"Composed '{_composeSourceName}' as '{req.SlotName}'.");
            await LoadTemplatesAsync();
        }
        else
        {
            _composeError = result.Error;
        }
    }

    // ---- Composition leaf: rename + delete ----
    private async Task RenameComposition(TemplateComposition composition)
    {
        var newName = await Dialog.PromptAsync(
            "Rename slot",
            $"New name for slot '{composition.InstanceName}':",
            initialValue: composition.InstanceName,
            placeholder: "Slot name");
        if (string.IsNullOrWhiteSpace(newName) || newName.Trim() == composition.InstanceName) return;

        var user = await GetCurrentUserAsync();
        var result = await TemplateService.RenameCompositionAsync(composition.Id, newName.Trim(), user);
        if (result.IsSuccess)
        {
            _toast.ShowSuccess($"Slot renamed to '{newName.Trim()}'.");
            await LoadTemplatesAsync();
        }
        else
        {
            _toast.ShowError(result.Error);
        }
    }

    private async Task DeleteComposition(TemplateComposition composition)
    {
        var confirmed = await Dialog.ConfirmAsync(
            "Delete composition",
            $"Delete slot '{composition.InstanceName}'? This removes the derived template and any overrides on it.",
            danger: true);
        if (!confirmed) return;

        var user = await GetCurrentUserAsync();
        var result = await TemplateService.DeleteCompositionAsync(composition.Id, user);
        if (result.IsSuccess)
        {
            _toast.ShowSuccess($"Composition '{composition.InstanceName}' removed.");
            await LoadTemplatesAsync();
        }
        else
        {
            _toast.ShowError(result.Error);
        }
    }
}
