<script>
    import { onDestroy, onMount } from 'svelte';
    import { useTinyRouter } from 'svelte-tiny-router';

    import { api, eventStream, target } from '../../urls.svelte.js';
    import { error, messages, yikes }  from '../../warn.svelte';
    import { t }             from '../../translations.svelte.js';
    import { user }          from '../../user.svelte.js';

    import Card              from './KanbanCard.svelte';
    import LineEditor        from '../../Components/LineEditor.svelte';
    import MarkdownEditor    from '../../Components/MarkdownEditor.svelte';
    import TaskForm          from '../task/Add.svelte';

	let eventSource      = null;
    let connectionStatus = 'disconnected';
    let { id }           = $props();
    let descr            = $state(false);
    let filter_input     = $state('');
    let router           = useTinyRouter();
    if (router.hasQueryParam('filter')) filter_input = router.getQueryParam('filter');
    let dragged      = null;
    let highlight    = $state({});
    let filter       = $derived(filter_input.toLowerCase());
    let project      = $state(null);
    let tasks        = $state({});
    let users        = [];
    let columns      = $derived(project.allowed_states?Object.keys(project.allowed_states).length+1:1);
    let info         = $state(null);
    let task_form    = $state(false);
    let stateList = {};
    $effect(() => updateUrl(filter_input));

    function byName(a,b) {
        return a.name.localeCompare(b.name);
    }

    async function create(name,user_id,state){
        var url = api('task/add');
        let task = {
            description : '',
            members     : {},
            name        : name,
            project_id  : +id,
            status      : { code : +state}
        }
        task.members[user_id] = { permission: { name : 'ASSIGNEE' }};
        task.members[user.id] = { permission: { name : 'OWNER' }};
        const resp            = await fetch(url,{
            credentials : 'include',
            method      : 'POST',
            body        : JSON.stringify(task)
        });
        if (resp.ok) {
            task          = await resp.json();
            task.assignee = user_id;
            if (!tasks[user_id]) tasks[user_id] = {};
            if (!tasks[user_id][state]) tasks[user_id][state] = {};
            tasks[user_id][state][task.id] = task;
            yikes();
            return 'reset';
        } else {
            error(resp);
            return false;
        }
    }

    async function do_archive(ex){
        ex.preventDefault();
        var task   = dragged;
        const url  = api(`task/${task.id}`);
        const resp = await fetch(url,{
            credentials : 'include',
            method      : 'PATCH',
            body        : JSON.stringify({no_index:true})
        });
        delete highlight.archive;
        if (resp.ok){
            yikes();
            delete tasks[task.assignee][task.status][task.id]
        } else {
            error(resp);
        }
    }

    async function drop(user_id,state){
        let task  = dragged;
        dragged   = null;
        highlight = {};

        if (task.assignee == user_id && task.status == state) return; // no change
        let patch              = {members:{},status:+state}
        patch.members[user_id] = 'ASSIGNEE';
        const url = api(`task/${task.id}`);
        const resp = await fetch(url,{
            credentials : 'include',
            method      : 'PATCH',
            body        : JSON.stringify(patch)
        });
        if (resp.ok){
            yikes();
        } else {
            error(resp);
        }
    }

    function handleCreateEvent(evt){
        handleEvent(evt,'create');
    }

    function handleEvent(evt,method){
        let json = JSON.parse(evt.data);
        if (json.task && json.user){
            // drop from kanban
            for (let uid in tasks){
                if (!uid) continue;
                for (let state in tasks[uid]) delete tasks[uid][state][json.task.id];
            }

            // (re) add to kanban
            if (method != 'delete') processTask(json.task);

            // show notification
            if (json.user.id == user.id) {
                if (method == 'create') task_form = false; // task has been created by current user
            } else {
                let term = "user_updated_entity";
                if (method == 'create') term = "user_created_entity";
                if (method == 'delete') term = "user_deleted_entity";
                info = t(term,{user:json.user.name,entity:json.task.name});
                setTimeout(() => { info = null; },2500);
            }
        }
    }

	function handleDeleteEvent(evt){
        handleEvent(evt,'delete');
    }

    function handleUpdateEvent(evt){
        handleEvent(evt,'update');
    }

    function hover(ev,user_id,state){
        ev.preventDefault();
        highlight = {user:user_id,state:state};
    }

    function hover_archive(ev){
        ev.preventDefault();
        highlight.archive = true;
    }

    function is_custom(state){
        return [10,20,40,60,100].includes(state);
    }

    async function load(){
        try {
			eventSource = eventStream(handleCreateEvent,handleUpdateEvent,handleDeleteEvent);
            await loadProject();
            loadTasks({project_id:+id,parent_task_id:0});
        } catch (ignored) {}
    }

    async function loadProject(){
        const url  = api(`project/${id}`);
        const resp = await fetch(url,{credentials:'include'});
        if (resp.ok){
            const json = await resp.json();
            project = json;

            // current user first
            users.push(user);
            if (!tasks[user.id]) tasks[user.id] = tasks[user.id] = {};

            for (var member of Object.values(json.members).map(a => a.user).sort(byName)){
                if (member.id != user.id) users.push(member);
                if (!tasks[member.id]) tasks[member.id] = {};
                for (var state of Object.keys(json.allowed_states)) tasks[member.id][state] = {};
            }

            yikes();
        } else {
            error(resp);
        }
    }

    async function loadTasks(selector){
        const url            = api('task/list');
        selector.show_closed = true;
        selector.no_index    = true;
        var resp = await fetch(url,{
            credentials :'include',
            method      : 'POST',
            body        : JSON.stringify(selector)
        });
        if (resp.ok){
            var json = await resp.json();
            for (var task_id of Object.keys(json)) {
                let task     = json[task_id];
                processTask(task);
            }
        } else {
            error(resp);
        }
    }

    function openTask(task_id){
        window.open(`/task/${task_id}/view`, '_blank').focus();
    }

    function processTask(task){
		if (task.no_index) return;

        let state    = +task.status;
        let owner    = null;
        let assignee = null;

        for (var user_id of Object.keys(task.members)){
            var member = task.members[user_id];
            if (member.permission.name == 'OWNER')    owner    = +user_id;
            if (member.permission.name == 'ASSIGNEE') assignee = +user_id;
        }
        if (!assignee) assignee = owner;
        task.assignee = assignee;
        if (!tasks[assignee]) tasks[assignee] = {};
        if (!tasks[assignee][state]) tasks[assignee][state] = {};
        tasks[assignee][state][task.id] = task;
    }


    async function save_bookmark(){
        const user_ids = Object.values(project.members).map(member => member.user.id);
        const data = {
            url     : location.href,
            tags    : ['Kanban', project.name, filter_input],
            comment : `${project.name}: ${filter_input}`,
            share   : user_ids
        }
        const url  = api('bookmark');
        const resp = await fetch(url,{
            credentials : 'include',
            method      : 'POST',
            body        : JSON.stringify(data)
        });
        if (resp.ok) {
            yikes();
            router.navigate('/bookmark');
        } else {
            error(resp);
        }
    }

    function show_task_form(project_id,assignee,state_id){
        task_form = {project_id,assignee,state_id};
    }

    function updateUrl(){
        let url = window.location.origin + window.location.pathname;
        if (filter_input) url += '?filter=' + encodeURI(filter_input);
        window.history.replaceState(window.history.state, '', url);
    }

	onDestroy(() => {
		if (eventSource) eventSource.close();
	});
    onMount(load);
</script>

<svelte:head>
    <title>Umbrella – {project?.name}</title>
</svelte:head>

{#if project}
{#if task_form}
<div class="overlay">
    <TaskForm assignee={task_form.assignee} on_abort={ev => {task_form = false;}} project_id={task_form.project_id} state_id={task_form.state_id} />
</div>
{/if} <!-- task form -->

<h1 onclick={ev => router.navigate(`/project/${project.id}/view`)}>{project.name}</h1>
{/if}
{#if info}
<div class="info">{info}</div>
{/if}
{#if project}
<fieldset class="kanban description {descr?'active':''}" onclick={e => descr = !descr}>
    <legend>{t('description')} – {t('expand_on_click')}</legend>
    {@html target(project.description.rendered)}
</fieldset>
<div class="kanban" style="display: grid; grid-template-columns: {`repeat(${columns}, auto)`}">
    <span class="filter">
        <input type="text" bind:value={filter_input} autofocus />
        {t('filter')}
        <button style="visibility:{filter_input ? 'visible' : 'hidden'}" onclick={save_bookmark}>
            <span class="symbol"></span> {t('save_object',{object:t('bookmark')})}
        </button>
    </span>
    <div class="head">{t('user')}</div>
    {#if project.allowed_states}
    {#each Object.entries(project.allowed_states) as [sid,state]}
    <div class="head">{sid%10?state:t('state_'+state.toLowerCase())}</div>
    {/each}
    {/if}
    {#each users as u}
        <div class="user">{u.name}</div>
        {#each Object.entries(project.allowed_states) as [state,name]}
            <div class={['state_'+state, is_custom(state) ? '':'state_custom' ,highlight.user == u.id && highlight.state == state ? 'highlight':'']} ondragover={ev => hover(ev,u.id,state)} ondragleave={e => delete highlight.user} ondrop={ev => drop(u.id,state)} >
                {#each Object.values(tasks[u.id][state]).sort(byName) as task}
                    {#if !filter || task.name.toLowerCase().includes(filter) || (task.tags && task.tags.filter(tag => tag.toLowerCase().includes(filter)).length)}
                    <Card onclick={e => openTask(task.id)} ondragstart={ev => dragged=task} {task} tag_colors={project.tag_colors} />
                    {/if}
                {/each}
                <div class="add_task" onclick={ev => show_task_form(project.id,u.id,+state)}>
                    {t('add_object',{object:t('task')})}
                </div>
            </div>
        {/each}
    {/each}
</div>
<div class="archive {highlight.archive?'hover':''}" ondragover={hover_archive} ondragleave={e => delete highlight.archive} ondrop={do_archive} >
{t('hide')}
</div>
{/if} <!-- project -->