001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package dev.enola.tool.todo;
019
020import com.fasterxml.jackson.annotation.JsonIgnore;
021import com.google.auto.value.AutoBuilder;
022import com.google.common.collect.ImmutableList;
023import com.google.common.collect.ImmutableMap;
024
025import dev.enola.data.id.UUID_IRI;
026
027import org.jspecify.annotations.Nullable;
028
029import java.net.URI;
030import java.time.Instant;
031import java.util.List;
032import java.util.Map;
033import java.util.Optional;
034
035public record ToDo(
036        // TODO implements Identifiable<URI>
037        URI id,
038        String title,
039        Optional<String> description,
040        ImmutableList<String> tags,
041        ImmutableMap<String, String> attributes,
042        Optional<Instant> created,
043        Optional<Instant> completed,
044        Optional<Byte> priority,
045
046        /** Assignee URI; e.g. <code>mailto:user@example.org</code>. */
047        Optional<URI> assignee,
048
049        /** Parent ToDo ID; e.g. a "Project". */
050        // TODO Add other more flexible relationships, e.g. "depends on", "related to" etc.
051        // TODO public Set<URI> children = new HashSet<>();
052        Optional<URI> parent
053
054        // TODO Discussion, like in a bug tracker, with comments by users etc.
055
056        // TODO public Set<URI> attachments = new HashSet<>();
057        ) {
058
059    public ToDo {
060        if (id == null) throw new IllegalArgumentException("id is required");
061        if (title == null) throw new IllegalArgumentException("title is required");
062        if (description == null) description = Optional.empty();
063        if (tags == null) tags = ImmutableList.of();
064        if (attributes == null) attributes = ImmutableMap.of();
065        if (created == null) created = Optional.empty();
066        if (completed == null) completed = Optional.empty();
067        if (priority == null) priority = Optional.empty();
068        if (assignee == null) assignee = Optional.empty();
069        if (parent == null) parent = Optional.empty();
070    }
071
072    @JsonIgnore
073    public boolean isCompleted() {
074        return completed.isPresent();
075    }
076
077    public static Builder builder() {
078        return new AutoBuilder_ToDo_Builder();
079    }
080
081    public Builder toBuilder() {
082        return builder().from(this);
083    }
084
085    @AutoBuilder(callMethod = "create")
086    public abstract static class Builder {
087        public abstract Builder id(URI id);
088
089        public abstract Builder title(String title);
090
091        public abstract Builder description(String description);
092
093        public abstract Builder tags(List<String> tags);
094
095        public abstract Builder attributes(Map<String, String> attributes);
096
097        public abstract Builder created(Instant created);
098
099        public abstract Builder completed(Instant completed);
100
101        public abstract Builder priority(Byte priority);
102
103        public abstract Builder assignee(URI assignee);
104
105        public abstract Builder parent(URI parent);
106
107        public Builder from(ToDo todo) {
108            id(todo.id());
109            title(todo.title());
110            todo.description().ifPresent(this::description);
111            tags(todo.tags());
112            attributes(todo.attributes());
113            todo.created().ifPresent(this::created);
114            todo.completed().ifPresent(this::completed);
115            todo.priority().ifPresent(this::priority);
116            todo.assignee().ifPresent(this::assignee);
117            todo.parent().ifPresent(this::parent);
118            return this;
119        }
120
121        public abstract ToDo build();
122    }
123
124    static ToDo create(
125            @Nullable URI id,
126            String title,
127            @Nullable Optional<String> description,
128            @Nullable List<String> tags,
129            @Nullable Map<String, String> attributes,
130            @Nullable Optional<Instant> created,
131            @Nullable Optional<Instant> completed,
132            @Nullable Optional<Byte> priority,
133            @Nullable Optional<URI> assignee,
134            @Nullable Optional<URI> parent) {
135
136        // TODO Generate a Multihash from content...
137        if (id == null) id = new UUID_IRI().toURI();
138
139        if (created == null || created.isEmpty()) {
140            created = Optional.of(Instant.now());
141        }
142
143        return new ToDo(
144                id,
145                title,
146                description == null ? Optional.empty() : description,
147                tags == null ? ImmutableList.of() : ImmutableList.copyOf(tags),
148                attributes == null ? ImmutableMap.of() : ImmutableMap.copyOf(attributes),
149                created,
150                completed == null ? Optional.empty() : completed,
151                priority == null ? Optional.empty() : priority,
152                assignee == null ? Optional.empty() : assignee,
153                parent == null ? Optional.empty() : parent);
154    }
155}