# app.py
from flask import Flask, request, jsonify, render_template
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from datetime import date, datetime, timedelta
import os
from apscheduler.executors.pool import ThreadPoolExecutor, ProcessPoolExecutor
from apscheduler.schedulers.background import BackgroundScheduler
import logging
import requests

app = Flask(__name__)
basedir = os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'app.db')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)
migrate = Migrate(app, db)


#####################################################################* ARCHIVING TASKS #####################################################################
def archive_tasks():
    with app.app_context():
        # Check last archive date
        last_archived_date = ArchiveInfo.get_last_archive()
        today = date.today()

        # If the last archive was not today, archive and update the record
        if last_archived_date is None or last_archived_date != today:
            # Perform the archive
            tasks = Task.query.filter(Task.status == 'finished')
            for task in tasks:
                task.status = 'archived'
                if task.completion_date == None:
                    task.completion_date = date.today() - timedelta(days=1)
            db.session.commit()

            # Update the archive info in the database
            ArchiveInfo.update_last_archive(today)
            logging.info(f"Archived tasks for {today}")
        

# Configure logging
logging.basicConfig(level=logging.INFO)
logging.getLogger('apscheduler').setLevel(logging.DEBUG)

# Initialize the scheduler
executors = {
    'default': ThreadPoolExecutor(20),
    'processpool': ProcessPoolExecutor(5)
}

job_defaults = {
    'coalesce': True, 
    'max_instances': 1
}

scheduler = BackgroundScheduler(executors=executors, job_defaults=job_defaults)
scheduler.add_job(archive_tasks, 'cron', hour=0, minute=1)  # Runs at 12:01 AM
# Periodic job to check every 15 minutes if archiving was missed
scheduler.add_job(archive_tasks, 'interval', minutes=15)
scheduler.start()

#####################################################################* DB CLASSES #####################################################################
class ArchiveInfo(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    last_archive = db.Column(db.Date, nullable=False)

    @staticmethod
    def get_last_archive():
        archive_info = ArchiveInfo.query.first()
        if archive_info:
            return archive_info.last_archive
        return None

    @staticmethod
    def update_last_archive(new_archive_date):
        archive_info = ArchiveInfo.query.first()
        if not archive_info:
            archive_info = ArchiveInfo(last_archive=new_archive_date)
            db.session.add(archive_info)
        else:
            archive_info.last_archive = new_archive_date
        db.session.commit()

class Task(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    status = db.Column(db.String(20), nullable=False)
    completion_date = db.Column(db.Date)
    column = db.Column(db.String(20), default='To Do')
    def to_dict(self):
        return {
            'id': self.id,
            'title': self.title,
            'status': self.status,
            'completion_date': self.completion_date.isoformat() if self.completion_date else None,
            'column': self.column
        }

class Habit(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(100), nullable=False)
    #days that the habit is available
    days = db.Column(db.String(100))  # Store as comma-separated string
    #list of completed dates
    dates = db.relationship('HabitDate', backref='habit', lazy=True)

class HabitDate(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    habit_id = db.Column(db.Integer, db.ForeignKey('habit.id'), nullable=False)
    date = db.Column(db.Date, nullable=False)

class JournalEntry(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(100), nullable=False)
    content = db.Column(db.Text, nullable=False)
    date = db.Column(db.Date, nullable=False)
    ai_comment = db.Column(db.Text)

#####################################################################* BASE ROUTES #####################################################################
@app.route('/')
def index():
    return render_template('index.html')

@app.route('/yearly-grid')
def yearly_grid():
    return render_template('yearly_grid.html')

#####################################################################* API routes #####################################################################
############################################## TASKS ##############################################
@app.route('/api/tasks', methods=['GET', 'POST'])
def handle_tasks():
    '''
    POST request: Creates a new task in the database and returns its details.
    GET  request: Retrieves and returns all tasks from the database as a list in JSON format.
    '''
    if request.method == 'POST':
        data = request.json
        new_task = Task(
            title=data['title'],
            status=data['status'],
            column=data.get('column', 'To Do')
        )
        db.session.add(new_task)
        db.session.commit()
        return jsonify(id=new_task.id, title=new_task.title, status=new_task.status, column=new_task.column)
    else:
        tasks = Task.query.all()
        return jsonify([{
            'id': task.id,
            'title': task.title,
            'status': task.status,
            'completion_date': str(task.completion_date) if task.completion_date else None,
            'column': task.column
        } for task in tasks])

@app.route('/api/tasks/<int:task_id>', methods=['PUT', 'DELETE'])
def update_task(task_id):
    task = Task.query.get_or_404(task_id)
    if request.method == 'PUT':
        data = request.json
        if 'title' in data and not data['title'].strip():
            # If the title is empty, delete the task
            db.session.delete(task)
            db.session.commit()
            return jsonify({'success': True, 'deleted': True})
        else:
            task.title = data.get('title', task.title)
            task.status = data.get('status', task.status)
            task.column = data.get('column', task.column)
            if task.status == 'finished':
                task.completion_date = date.today()
            else:
                task.completion_date = None
            db.session.commit()
            return jsonify({'success': True, 'deleted': False})
    elif request.method == 'DELETE':
        db.session.delete(task)
        db.session.commit()
        return jsonify({'success': True})
    
@app.route('/api/tasks/archive', methods=["POST"])
def archive_completed_tasks():
    tasks = Task.query.filter(Task.status == 'finished')
    for task in tasks:
        task.status = 'archived'
        if task.completion_date == None:
            task.completion_date = date.today() - timedelta(days=1)
    db.session.commit()
    return jsonify({'success': True})

############################################## HABITS ##############################################

@app.route('/api/habits', methods=['GET', 'POST'])
def handle_habits():
    '''
    POST request: Creates a new habit in db and returns related JSON
    GET  request: Returns a JSON of all habits and related fields
    '''
    if request.method == 'POST':
        data = request.json
        new_habit = Habit(name=data['name'], days=','.join(map(str, data.get('days', []))))
        db.session.add(new_habit)
        db.session.commit()
        return jsonify(id=new_habit.id, name=new_habit.name, days=new_habit.days.split(','))
    else:
        habits_data = []
        for habit in Habit.query.all():
            habit_data = {
                'id': habit.id,
                'name': habit.name,
                'days': habit.days.split(',') if habit.days else [],
                'dates': [habit_date.date.isoformat() for habit_date in habit.dates]
            }
            habits_data.append(habit_data)
        
        return jsonify(habits_data)

@app.route('/api/habits/<int:habit_id>', methods=['PUT', 'DELETE'])
def update_habit(habit_id):
    habit = Habit.query.get_or_404(habit_id)
    if request.method == 'PUT':
        data = request.json
        if 'name' in data:
            habit.name = data['name']
        if 'days' in data:
            habit.days = ','.join(map(str, data['days']))
        if 'toggle_date' in data:
            toggle_date = date.fromisoformat(data['toggle_date'])
            existing_date = HabitDate.query.filter_by(habit_id=habit.id, date=toggle_date).first()
            if existing_date:
                db.session.delete(existing_date)
            else:
                new_date = HabitDate(habit_id=habit.id, date=toggle_date)
                db.session.add(new_date)
        db.session.commit()
        return jsonify({'success': True})
    elif request.method == 'DELETE':
        db.session.delete(habit)
        db.session.commit()
        return jsonify({'success': True})

############################################## JOURNAL ##############################################
@app.route('/api/journal', methods=['GET', 'POST'])
def handle_journal():
    if request.method == 'POST':
        data = request.json
        new_entry = JournalEntry(
            title=data['title'],
            content=data['content'],
            date=date.today(),
            ai_comment=data.get('ai_comment', '')
        )
        db.session.add(new_entry)
        db.session.commit()
        return jsonify(id=new_entry.id, title=new_entry.title, content=new_entry.content, date=str(new_entry.date), ai_comment=new_entry.ai_comment)
    else:
        entries = JournalEntry.query.all()
        return jsonify([{
            'id': entry.id,
            'title': entry.title,
            'content': entry.content,
            'date': str(entry.date),
            'ai_comment': entry.ai_comment
        } for entry in entries])

@app.route('/api/journal/<int:entry_id>', methods=['PUT', 'DELETE'])
def update_journal(entry_id):
    entry = JournalEntry.query.get_or_404(entry_id)
    if request.method == 'PUT':
        data = request.json
        entry.title = data.get('title', entry.title)
        entry.content = data.get('content', entry.content)
        entry.ai_comment = data.get('ai_comment', entry.ai_comment)
        db.session.commit()
        return jsonify({'success': True})
    elif request.method == 'DELETE':
        db.session.delete(entry)
        db.session.commit()
        return jsonify({'success': True})
    
############################################## HISTORY ##############################################

@app.route('/api/history', methods=['GET'])
def get_yearly_data():
    year_param = request.args.get('year', default=date.today().year, type=int)
    
    # Define the start and end dates for the specified year
    start_date = date(year_param, 1, 1)
    end_date = date(year_param, 12, 31)
    
    # Handle future years gracefully
    today = date.today()
    if end_date > today:
        end_date = today  # Limit end_date to today if in future
        
    # Generate the date range for the specified year
    total_days = (end_date - start_date).days + 1
    yearly_data = {
        (start_date + timedelta(days=i)).isoformat(): {
            "availableHabits": 0,
            "completedHabits": 0,
            "completedTasks": [],
            "completedHabitsList": [],
            "completionRate": 0
        } for i in range(total_days)
    }
    
    habits = Habit.query.all()
    tasks = Task.query.filter(
        Task.completion_date >= start_date,
        Task.completion_date <= end_date
    ).all()
    journal_entries = JournalEntry.query.all()
    
    # Build a mapping of dates to available habits
    for habit in habits:
        habit_days = habit.days.split(',') if habit.days else []
        for date_str in yearly_data.keys():
            date_obj = date.fromisoformat(date_str)
            # Adjusting weekday calculation since strftime('%w') returns 0 for Sunday
            weekday = str((date_obj.weekday() + 1) % 7)
            if weekday in habit_days:
                yearly_data[date_str]["availableHabits"] += 1
                
    # Mark completed habits
    for habit in habits:
        for habit_date in habit.dates:
            if start_date <= habit_date.date <= end_date:
                date_str = habit_date.date.isoformat()
                if date_str in yearly_data:
                    yearly_data[date_str]["completedHabits"] += 1
                    yearly_data[date_str]["completedHabitsList"].append(habit.name)
    
     # Record completed tasks
    for task in tasks:
        date_str = task.completion_date.isoformat()
        if date_str in yearly_data:
            yearly_data[date_str]["completedTasks"].append(task.title)
            
    # Calculate completion rate
    for date_str, data in yearly_data.items():
        available = data["availableHabits"]
        completed = data["completedHabits"]
        data["completionRate"] = completed / available if available > 0 else None
    
    for entry in journal_entries:
        date_str = str(entry.date)
        if date_str not in yearly_data:
            yearly_data[date_str] = {"completedHabits": [], "completedTasks": [], "journalEntries": []}
        yearly_data[date_str]["journalEntries"].append(entry.title)
    
    for date_str, data in yearly_data.items():
        available = data["availableHabits"]
        completed = data["completedHabits"]
        if available > 0:
            data["completionRate"] = completed / available
        else:
            data["completionRate"] = None
    
    # Return the data with date strings as keys
    return jsonify(yearly_data)

if __name__ == '__main__':
    with app.app_context():
        db.create_all()
    app.run(debug=True)

yearly_grid.js:
let currentYear = new Date().getFullYear();
let displayMode = 'combined';
const dayWidth = 30;  // Updated width of each day square
const dayHeight = 30; // Updated height of each day square
const gutter = 2;     // Space between squares

document.addEventListener('DOMContentLoaded', function() {
    // Set the current year display
    document.getElementById('current-year').textContent = currentYear;

    // Add event listeners to navigation buttons
    document.getElementById('prev-year').addEventListener('click', () => {
        currentYear--;
        updateYearlyGrid();
    });

    document.getElementById('next-year').addEventListener('click', () => {
        currentYear++;
        updateYearlyGrid();
    });

    // Add event listener to the display mode toggle
    const displayModeSelect = document.getElementById('display-mode');
    displayModeSelect.value = displayMode; // Set default value
    displayModeSelect.addEventListener('change', () => {
        displayMode = displayModeSelect.value;
        updateYearlyGrid();
    });

    // Fetch the initial data
    updateYearlyGrid();
});

function updateYearlyGrid() {
    document.getElementById('current-year').textContent = currentYear;

    // Disable the next button if currentYear is the same as the current year
    const nextYearButton = document.getElementById('next-year');
    if (currentYear >= new Date().getFullYear()) {
        nextYearButton.disabled = true;
    } else {
        nextYearButton.disabled = false;
    }

    fetchHistoryData(currentYear);
}

function fetchHistoryData(year) {
    axios.get(`/api/history?year=${year}`)
        .then(response => {
            const data = response.data;
            renderHeatmap(data, year, displayMode); // Pass displayMode
        })
        .catch(error => {
            console.error('Error fetching history data:', error);
            alert('No data available for this year.');
        });
}

function renderHeatmap(data, year, displayMode) {
    const container = document.getElementById('heatmap-container');
    container.innerHTML = '';
    const currentDate = new Date();

    const dataMap = new Map(Object.entries(data));

    // Create the main grid container
    const grid = document.createElement('div');
    grid.classList.add('heatmap-grid');

    // Iterate over each month
    for (let month = 0; month < 12; month++) {
        if (currentYear >= currentDate.getFullYear() && month > currentDate.getMonth()) {
            break;
        }

        const monthColumn = document.createElement('div');
        monthColumn.classList.add('month-column');

        const monthDate = new Date(year, month, 1);
        const monthName = monthDate.toLocaleString('default', { month: 'short' });

        // Create the month label
        const monthLabel = document.createElement('div');
        monthLabel.classList.add('month-label');
        monthLabel.textContent = monthName;
        monthColumn.appendChild(monthLabel);

        // Optionally, add weekday labels
        const weekdaysRow = document.createElement('div');
        weekdaysRow.classList.add('weekdays-row');
        const weekdays = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
        weekdays.forEach(dayName => {
            const weekdayLabel = document.createElement('div');
            weekdayLabel.classList.add('weekday-label');
            weekdayLabel.textContent = dayName;
            weekdaysRow.appendChild(weekdayLabel);
        });
        monthColumn.appendChild(weekdaysRow);

        // Get the weekday of the first day of the month
        const firstWeekday = monthDate.getDay();

        // Create a grid for days
        const daysGrid = document.createElement('div');
        daysGrid.classList.add('days-grid');

        // Add empty cells for days before the first of the month
        for (let i = 0; i < firstWeekday; i++) {
            const emptyCell = document.createElement('div');
            emptyCell.classList.add('day-cell', 'empty-cell');
            daysGrid.appendChild(emptyCell);
        }

        // Get total days in the month
        const daysInMonth = new Date(year, month + 1, 0).getDate();

        // Create day cells
        for (let day = 1; day <= daysInMonth; day++) {

            if (currentYear >= currentDate.getFullYear() && (month == currentDate.getMonth() && day > currentDate.getDate() || (month > currentDate.getMonth()))) {
                break;
            }

            const date = new Date(year, month, day);
            const dateStr = date.toISOString().split('T')[0];

            const dayCell = document.createElement('div');
            dayCell.classList.add('day-cell');
            dayCell.dataset.date = dateStr;

            const dayData = dataMap.get(dateStr);
            let shade = '#444';

            if (dayData) {
                if (displayMode === 'combined') {
                    shade = getCombinedShade(dayData);
                } else if (displayMode === 'habits') {
                    shade = getHabitShade(dayData);
                } else if (displayMode === 'tasks') {
                    shade = getTaskShade(dayData);
                }
            } else {
                shade = '#444'; // Default shade when no data
            }
            dayCell.style.backgroundColor = shade;

            // Make the cell clickable
            dayCell.classList.add('clickable');
            dayCell.addEventListener('click', () => {
                if (dayData) {
                    showDetailsModal(dateStr, dayData);
                } else {
                    showDetailsModal(dateStr, {
                        availableHabits: 0,
                        completedHabits: 0,
                        completedTasks: [],
                        completedHabitsList: [],
                        completionRate: null
                    });
                }
            });

            daysGrid.appendChild(dayCell);
        }

        monthColumn.appendChild(daysGrid);
        grid.appendChild(monthColumn);
    }

    // Append grid to container
    container.appendChild(grid);

    // Remove calls to renderMonthLabels and renderWeekdayLabels
    // renderMonthLabels(container, startDate, endDate, totalWeeks);
    // renderWeekdayLabels(container);
}

////////////////////* https://yeun.github.io/open-color/ for colors *////////////////////////
function getCombinedShade(dayData) {
    const shades = [
        '#3b3b3b', // No tasks completed
        '#106030', // 1 task
        '#2b8a3e', // 2 tasks
        '#2f9e44', // 3 tasks
        '#37b24d', // 4 tasks
        '#40c057', // 5 tasks
        '#51cf66', // 6 tasks
        '#69db7c', // 7 tasks
        '#8ce99a', // 8 tasks
        '#b2f2bb', // 9 tasks
        '#d3f9d8', // 10 tasks
        '#ebfbee', // 11+ tasks
    ];

    if (!dayData || dayData.completedTasks.length + dayData.completedHabits === 0) {
        return shades[0];
    } else {
        const combinedCompleted = dayData.completedTasks.length + dayData.completedHabits;
        if (combinedCompleted === 1) {
            return shades[1];
        } else if (combinedCompleted === 2) {
            return shades[2];
        } else if (combinedCompleted === 3) {
            return shades[3];
        } else if (combinedCompleted === 4) {
            return shades[4];
        } else if (combinedCompleted === 5) {
            return shades[5];
        } else if (combinedCompleted === 6) {
            return shades[6];
        } else if (combinedCompleted === 7) {
            return shades[7];
        } else if (combinedCompleted === 8) {
            return shades[8];
        } else if (combinedCompleted === 9) {
            return shades[9];
        } else if (combinedCompleted === 10) {
            return shades[10];
        } else {
            return shades[11];
        }
    }
}
function getHabitShade(dayData) {
    const shades = [
        '#2c2c2c', // No habits available (darkest)
        '#3b3b3b', // 0% completion
        '#1864ab', // 10% completion
        '#1971c2', // 25% completion
        '#1c7ed6', // 40% completion
        '#228be6', // 65% completion
        '#339af0', // 75% completion
        '#4dabf7', // 85% completion
        '#74c0fc'  // 100% completion
    ];

    if (!dayData || dayData.completionRate === null) {
        return shades[0]; // No habits available
    } else {
        const completionRate = dayData.completionRate;
        if (completionRate === 0) {
            return shades[1];
        } else if (completionRate <= 0.1) {
            return shades[2];
        } else if (completionRate <= 0.25) {
            return shades[3];
        } else if (completionRate <= 0.40) {
            return shades[4];
        } else if (completionRate <= 0.65) {
            return shades[5];
        } else if (completionRate <= 0.75) {
            return shades[6];
        } else if (completionRate <= 0.85) {
            return shades[7];
        } else {
            return shades[8];
        }
    }
}
function getTaskShade(dayData) {
    const shades = [
        '#3b3b3b', // No tasks completed
        '#3E0101', // 1 task
        '#8B0000', // 2 tasks
        '#C92A2A', // 3 tasks
        '#E03030', // 4 tasks
        '#F03E3E', // 5 tasks
        '#FA5353', // 6 tasks
        '#FE6A6B', // 7 tasks
        '#FF8787', // 8 tasks
        '#FFC9C9', // 9 tasks
        '#FFE2E2', // 10 tasks
        '#FFF5F5', // 11+ tasks
    ];

    if (!dayData || dayData.completedTasks.length === 0) {
        return shades[0];
    } else {
        const tasksCompleted = dayData.completedTasks.length;
        if (tasksCompleted === 1) {
            return shades[1];
        } else if (tasksCompleted === 2) {
            return shades[2];
        } else if (tasksCompleted === 3) {
            return shades[3];
        } else if (tasksCompleted === 4) {
            return shades[4];
        } else if (tasksCompleted === 5) {
            return shades[5];
        } else if (tasksCompleted === 6) {
            return shades[6];
        } else if (tasksCompleted === 7) {
            return shades[7];
        } else if (tasksCompleted === 8) {
            return shades[8];
        } else if (tasksCompleted === 9) {
            return shades[9];
        } else if (tasksCompleted === 10) {
            return shades[10];
        } else {
            return shades[11];
        }
    }
}

function renderMonthLabels(container, startDate, endDate, totalWeeks) {
    const monthNames = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
                        'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];

    const monthsRow = document.createElement('div');
    monthsRow.classList.add('months-row');

    let currentMonth = -1;
    let weeksInMonth = 0;
    let weekIndex = 0;
    let dateIterator = new Date(startDate);

    while (dateIterator <= endDate) {
        const month = dateIterator.getMonth();
        if (month !== currentMonth) {
            // New month
            if (currentMonth !== -1) {
                // Set the width of the previous month label
                monthsRow.children[monthsRow.children.length - 1].style.width = (weeksInMonth * (dayWidth + gutter)) + 'px';
            }
            // Create new month label
            currentMonth = month;
            weeksInMonth = 1;

            const monthLabel = document.createElement('div');
            monthLabel.classList.add('month-label');
            monthLabel.textContent = monthNames[currentMonth];
            monthsRow.appendChild(monthLabel);
        } else {
            // Same month
            weeksInMonth++;
        }

        // Move to the next week
        dateIterator.setDate(dateIterator.getDate() + 7);
        weekIndex++;
    }

     // Set the width for the last month label
    monthsRow.children[monthsRow.children.length - 1].style.width = (weeksInMonth * (dayWidth + gutter)) + 'px';

    // Append monthsRow to container
    container.insertBefore(monthsRow, container.firstChild);
}

function renderWeekdayLabels(container) {
    const weekdayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];

    const weekdaysColumn = document.createElement('div');
    weekdaysColumn.classList.add('weekdays-column');

    for (let i = 0; i < 7; i++) {
        const dayLabel = document.createElement('div');
        dayLabel.classList.add('weekday-label');

        // Show labels for Sun, Tue, Thu (or adjust as desired)
        if (i === 0 || i === 2 || i === 4) {
            dayLabel.textContent = weekdayNames[i];
        } else {
            dayLabel.textContent = ''; // Empty label for alignment
        }

        dayLabel.style.height = dayHeight + 'px';
        dayLabel.style.margin = (gutter / 2) + 'px 0'; // Adjust vertical margin

        weekdaysColumn.appendChild(dayLabel);
    }

    // Append the weekdaysColumn to container
    container.appendChild(weekdaysColumn);
}

function showDetailsModal(dateStr, dayData) {
    const modal = document.getElementById('details-modal');
    const modalDate = document.getElementById('modal-date');
    const modalContent = document.getElementById('modal-content');
    const closeButton = document.getElementById('modal-close');

    modalDate.textContent = dateStr;
    modalContent.innerHTML = '';

    // Display completed habits
    const habitsDiv = document.createElement('div');
    habitsDiv.innerHTML = `<h3>Completed Habits (${dayData.completedHabits}/${dayData.availableHabits})</h3>`;
    const habitsList = document.createElement('ul');

    dayData.completedHabitsList.forEach(habitName => {
        const li = document.createElement('li');
        li.textContent = habitName;
        habitsList.appendChild(li);
    });
    
    if (dayData.completedHabitsList.length > 0) {
        habitsDiv.appendChild(habitsList);
    } else {
        const noHabits = document.createElement('p');
        noHabits.textContent = 'No habits completed on this day.';
        habitsDiv.appendChild(noHabits);
    }

    modalContent.appendChild(habitsDiv);

    // Display completed tasks
    if (dayData.completedTasks.length > 0) {
        const tasksDiv = document.createElement('div');
        tasksDiv.innerHTML = `<h3>Completed Tasks</h3>`;
        const tasksList = document.createElement('ul');

        dayData.completedTasks.forEach(taskTitle => {
            const li = document.createElement('li');
            li.textContent = taskTitle;
            tasksList.appendChild(li);
        });
        tasksDiv.appendChild(tasksList);
        modalContent.appendChild(tasksDiv);
    } else {
        const noTasks = document.createElement('p');
        noTasks.textContent = 'No tasks completed on this day.';
        modalContent.appendChild(noTasks);
    }

    // Show the modal
    modal.style.display = 'block';

    // Close modal when clicking on the close button
    closeButton.onclick = function() {
        modal.style.display = 'none';
    };

    // Close modal when clicking outside the modal content
    window.onclick = function(event) {
        if (event.target === modal) {
            modal.style.display = 'none';
        }
    };
}

script.js:
const columnOrder = ['to-do-week', 'to-do-today', 'in-progress', 'finished'];

function createTaskElement(task) {
  const taskElement = document.createElement('div');
  taskElement.className = 'task-card';
  taskElement.dataset.taskId = task.id;

  taskElement.innerHTML = `
    <div class="task-title" contenteditable="true" onblur="updateTaskTitle(${task.id}, this)">${task.title}</div>
    <div class="task-buttons">
      ${getButtonsForTask(task)}
    </div>
  `;
  return taskElement;
}

function initializeSortable() {
    columnOrder.forEach(columnId => {
      const columnElement = document.getElementById(columnId);
  
      Sortable.create(columnElement, {
        group: 'columns', // Enable moving items between columns
        animation: 150,
        handle: '.task-card',
        draggable: '.task-card',
        onEnd: function (evt) {
          // Update task order in the client-side state here
          // Optionally, save to localStorage or send to the server when appropriate
        },
      });
    });
}
  
function loadTasks() {
    axios.get('/api/tasks')
      .then(response => {
        const tasks = response.data;
  
        // Clear columns
        columnOrder.forEach(status => {
          const column = document.getElementById(status);
          column.innerHTML = `
            <div class="column-header">
              <span class="column-title">${column.id.split('-').map(word => word.charAt(0).toUpperCase() + word.slice(1)).join(' ')}</span>
              <span>...</span>
            </div>
          `;
        });
  
        tasks.forEach(task => {
          const columnElement = document.getElementById(task.status);
          if (!columnElement) return;
  
          const taskElement = createTaskElement(task);
          columnElement.appendChild(taskElement);
        });
  
        // Add "Add a card" buttons to columns
        columnOrder.forEach(status => {
          const column = document.getElementById(status);
          const addCard = document.createElement('div');
          addCard.className = 'add-card';
          addCard.textContent = '+ Add a card';
          addCard.onclick = () => addTask(status);
          column.appendChild(addCard);
        });
  
        // Initialize Sortable
        initializeSortable();
      })
      .catch(error => {
        console.error('Error loading tasks:', error);
      });
  }

function getButtonsForTask(task) {
    switch (task.status) {
        case 'to-do-week':
            return `<button class="task-button" onclick="moveTask(${task.id}, 'to-do-today')">→ Today</button>`;
        case 'to-do-today':
            return `
                <button class="task-button" onclick="moveTask(${task.id}, 'in-progress')">▶ IP</button>
                <button class="task-button" onclick="moveTask(${task.id}, 'finished')">✓</button>
            `;
        case 'in-progress':
            return `
                <button class="task-button" onclick="moveTask(${task.id}, 'to-do-week')">↺ Week</button>
                <button class="task-button" onclick="moveTask(${task.id}, 'to-do-today')">↺ Today</button>
                <button class="task-button" onclick="moveTask(${task.id}, 'finished')">✓</button>
            `;
        case 'finished':
            return `
                <button class="task-button" onclick="moveTask(${task.id}, 'to-do-week')">↺ Week</button>
                <button class="task-button" onclick="moveTask(${task.id}, 'to-do-today')">↺ Today</button>
            `;
        default:
            return '';
    }
}

function addTask(status) {
    const column = document.getElementById(status);
    
    // Hide the "Add a card" button
    const addCardButton = column.querySelector('.add-card');
    addCardButton.style.display = 'none';

    // Create a new task card div
    const newCard = document.createElement('div');
    newCard.classList.add('task-card');
    newCard.contentEditable = 'true';  // Make the new card editable

    // Add event listener to handle saving when 'Enter' is pressed
    newCard.addEventListener('keydown', function(event) {
        if (event.key === 'Enter') {
            event.preventDefault();  // Prevent new lines when pressing Enter
            const taskTitle = newCard.textContent.trim();
            if (taskTitle) {
                // Send task title and status to the backend
                axios.post('/api/tasks', { title: taskTitle, status })
                    .then(() => {
                        loadTasks();  // Reload tasks after adding new one
                        addCardButton.style.display = 'block';  // Show "Add a card" button again
                    });
            } else {
                // Remove the card if no text was entered
                newCard.remove();
                addCardButton.style.display = 'block';  // Show "Add a card" button again
            }
        }
    });

    // Add event listener for "Escape" key to discard the card
    newCard.addEventListener('keydown', function(event) {
        if (event.key === 'Escape') {
            newCard.remove();  // Discard the new card
            addCardButton.style.display = 'block';  // Show "Add a card" button again
        }
    });

    // Append the new card to the column
    column.appendChild(newCard);
    // Focus on the new card to allow typing immediately
    newCard.focus();
}

function moveTask(taskId, newStatus) {
    axios.put(`/api/tasks/${taskId}`, { status: newStatus })
        .then(() => loadTasks());
}

function updateTaskTitle(taskId, element) {
    const newTitle = element.textContent.trim();
    axios.put(`/api/tasks/${taskId}`, { title: newTitle })
    .then((response) => {
        if (response.data.deleted) {
            // If the task was deleted (empty title), remove it from the UI
            element.closest('.task-card').remove();
        } else {
            // If the task was updated, reload tasks to reflect any changes
            loadTasks();
        }
    })
    .catch((error) => {
        console.error('Error updating task:', error);
        loadTasks(); // Reload tasks to ensure UI is in sync with server
    });
}

function loadHabits() {
    axios.get('/api/habits')
        .then(response => {
            const habits = response.data;
            const habitsContainer = document.getElementById('habits');
            habitsContainer.innerHTML = '';
            const today = new Date();
            const todayStr = today.toISOString().split('T')[0]; // 'YYYY-MM-DD'
            const todayWeekday = today.getDay().toString(); // '0' (Sunday) to '6' (Saturday)

            habits.forEach(habit => {
                const isAvailableToday = habit.days.includes(todayWeekday);
                if (!isAvailableToday) {
                    // Skip habits not scheduled for today
                    return;
                }

                const habitElement = document.createElement('div');
                const habitDates = habit.dates || [];
                const isCompletedToday = habitDates.includes(todayStr);

                habitElement.className = `habit ${isCompletedToday ? 'habit-complete' : 'habit-incomplete'}`;
                habitElement.innerHTML = `<h3>${habit.name}</h3>`;
                habitElement.onclick = () => toggleHabit(habitElement, habit.id);
                habitsContainer.appendChild(habitElement);
            });
        })
        .catch(error => {
            console.error('Error loading habits:', error);
        });
}

function addHabit() {
    const name = document.getElementById('new-habit').value;
    const dayCheckboxes = document.querySelectorAll('#day-select input[type="checkbox"]:checked');
    const days = Array.from(dayCheckboxes).map(cb => cb.value);
    // console.log(days)
    if (name) {
        axios.post('/api/habits', { name, days })
            .then(() => {
                document.getElementById('new-habit').value = '';
                // Clear checkboxes
                document.querySelectorAll('#day-select input[type="checkbox"]').forEach(cb => cb.checked = false);
                loadHabits();
            });
    }
}

function toggleHabit(element, habitId) {
    const today = new Date().toISOString().split('T')[0];

    // Toggle the visual state immediately
    const isCurrentlyCompleted = element.classList.contains('habit-complete');

    if (isCurrentlyCompleted) {
        // Remove 'habit-complete', add 'habit-incomplete'
        element.classList.remove('habit-complete');
        element.classList.add('habit-incomplete');
    } else {
        // Add 'habit-complete', remove 'habit-incomplete'
        element.classList.add('habit-complete');
        element.classList.remove('habit-incomplete');
    }

    // Send the request to the backend
    axios.put(`/api/habits/${habitId}`, { toggle_date: today })
        .then(response => {
            if (!response.data.success) {
                console.error(`Failed to toggle habit ${habitId}`);
                // Optionally revert the visual change if the backend fails
                toggleVisualState(element);
            }
        })
        .catch(error => {
            console.error('Error toggling habit:', error.response ? error.response.data : error.message);
            // Optionally revert the visual change if the backend fails
            toggleVisualState(element);
        });
}

// Helper function to revert visual state if needed
function toggleVisualState(element) {
    const isCompleted = element.classList.contains('habit-complete');
    if (isCompleted) {
        element.classList.remove('habit-complete');
        element.classList.add('habit-incomplete');
    } else {
        element.classList.add('habit-complete');
        element.classList.remove('habit-incomplete');
    }
}

document.addEventListener('DOMContentLoaded', function () {
    // Load tasks and habits on page load
    loadTasks();
    loadHabits();
});


The habits and tasks are misaligned by at least one day. The habits, if available the next day, do not toggle back to 'incomplete' automatically upon reaching the next day, and the tasks and habits show completion for the next day instead of the day it was accomplished. Let me know if you need any other files