package com.lemenzo.clock;

import android.app.*;
import android.content.*;
import android.graphics.Typeface;
import android.os.*;
import android.provider.AlarmClock;
import android.text.InputType;
import android.view.*;
import android.widget.*;

import com.lemenzo.clock.alarm.*;
import com.lemenzo.clock.bedtime.*;
import com.lemenzo.clock.model.Alarm;
import com.lemenzo.clock.stopwatch.StopwatchRepository;
import com.lemenzo.clock.timer.*;
import com.lemenzo.clock.util.Ui;
import com.lemenzo.clock.worldclock.WorldClockRepository;
import com.lemenzo.clock.worldclock.WorldClockFormatter;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;

public final class ClockActivity extends Activity {
    private static final int REQ_ALARM=500;
    private static final String TAB_ALARM="alarm",TAB_TIMER="timer",TAB_STOPWATCH="stopwatch",TAB_WORLD="world",TAB_BEDTIME="bedtime";
    private LinearLayout body,tabs;
    private AlarmRepository alarms;
    private StopwatchRepository stopwatchRepository;
    private TimerRepository timerRepository;
    private final Map<Long,TextView> timerCountdownViews=new HashMap<>();
    private final Handler handler=new Handler(Looper.getMainLooper());
    private String current=TAB_ALARM;
    private TextView stopwatchText;
    private boolean tickerRunning;

    private final Runnable ticker=new Runnable(){@Override public void run(){
        if(!tickerRunning)return;
        long nextDelay=1000L;
        if(TAB_STOPWATCH.equals(current)&&stopwatchText!=null){
            stopwatchText.setText(formatMs(stopwatchRepository.elapsed()));
            nextDelay=100L;
        }else if(TAB_TIMER.equals(current)){
            refreshTimerCountdowns();
            nextDelay=1000L;
        }
        handler.postDelayed(this,nextDelay);
    }};

    @Override protected void onCreate(Bundle b){super.onCreate(b);alarms=new AlarmRepository(this);stopwatchRepository=new StopwatchRepository(this);timerRepository=new TimerRepository(this);buildShell();handle(getIntent());}
    @Override protected void onNewIntent(Intent i){super.onNewIntent(i);setIntent(i);handle(i);}
    @Override protected void onResume(){super.onResume();tickerRunning=true;handler.removeCallbacks(ticker);handler.post(ticker);}
    @Override protected void onPause(){tickerRunning=false;handler.removeCallbacks(ticker);super.onPause();}
    @Override protected void onDestroy(){tickerRunning=false;handler.removeCallbacks(ticker);timerCountdownViews.clear();super.onDestroy();}
    @Override protected void onActivityResult(int req,int res,Intent data){super.onActivityResult(req,res,data);if(req==REQ_ALARM&&res==RESULT_OK)show(TAB_ALARM);}

    private void buildShell(){
        LinearLayout root=new LinearLayout(this);
        root.setOrientation(LinearLayout.VERTICAL);
        root.setBackgroundColor(Ui.BLACK);
        root.setPadding(Ui.dp(this,18),Ui.dp(this,10),Ui.dp(this,18),Ui.dp(this,10));

        LinearLayout header=new LinearLayout(this);
        header.setGravity(Gravity.CENTER_VERTICAL);
        header.setPadding(Ui.dp(this,2),Ui.dp(this,4),Ui.dp(this,2),Ui.dp(this,10));

        ImageView logo=new ImageView(this);
        logo.setImageResource(R.drawable.lemenzo_clock_logo);
        logo.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
        header.addView(logo,new LinearLayout.LayoutParams(Ui.dp(this,44),Ui.dp(this,44)));

        LinearLayout identity=new LinearLayout(this);
        identity.setOrientation(LinearLayout.VERTICAL);
        identity.setGravity(Gravity.CENTER_VERTICAL);
        identity.setPadding(Ui.dp(this,12),0,Ui.dp(this,8),0);
        TextView title=Ui.title(this,getString(R.string.app_titel),25);
        identity.addView(title,new LinearLayout.LayoutParams(-1,-2));
        TextView privacy=Ui.eyebrow(this,getString(R.string.privacy_badge));
        privacy.setPadding(0,Ui.dp(this,2),0,0);
        identity.addView(privacy,new LinearLayout.LayoutParams(-1,-2));
        header.addView(identity,new LinearLayout.LayoutParams(0,Ui.dp(this,62),1));

        Button settings=Ui.premiumIconButton(this,getString(R.string.settings));
        settings.setCompoundDrawablesWithIntrinsicBounds(0,0,R.drawable.ic_settings,0);
        settings.setOnClickListener(v->startActivity(new Intent(this,com.lemenzo.clock.settings.SettingsActivity.class)));
        header.addView(settings,new LinearLayout.LayoutParams(Ui.dp(this,48),Ui.dp(this,48)));
        root.addView(header);

        ScrollView sc=new ScrollView(this);
        sc.setFillViewport(true);
        sc.setClipToPadding(false);
        body=new LinearLayout(this);
        body.setOrientation(LinearLayout.VERTICAL);
        body.setPadding(0,Ui.dp(this,12),0,Ui.dp(this,30));
        sc.addView(body);
        root.addView(sc,new LinearLayout.LayoutParams(-1,0,1));

        tabs=new LinearLayout(this);
        tabs.setGravity(Gravity.CENTER);
        tabs.setPadding(Ui.dp(this,6),Ui.dp(this,6),Ui.dp(this,6),Ui.dp(this,6));
        tabs.setBackground(Ui.navBarBackground(this));
        addTab(TAB_ALARM,R.string.nav_alarm);
        addTab(TAB_TIMER,R.string.nav_timer);
        addTab(TAB_STOPWATCH,R.string.nav_stopwatch);
        addTab(TAB_WORLD,R.string.nav_world);
        addTab(TAB_BEDTIME,R.string.nav_bedtime);
        LinearLayout.LayoutParams navLp=new LinearLayout.LayoutParams(-1,Ui.dp(this,74));
        navLp.setMargins(0,Ui.dp(this,8),0,0);
        root.addView(tabs,navLp);

        setContentView(root);
        Ui.applySystemSafeInsets(this,root);
        show(TAB_ALARM);
    }
    private void addTab(String key,int label){
        int icon=TAB_ALARM.equals(key)?R.drawable.ic_nav_alarm:
                TAB_TIMER.equals(key)?R.drawable.ic_nav_timer:
                TAB_STOPWATCH.equals(key)?R.drawable.ic_nav_stopwatch:
                TAB_WORLD.equals(key)?R.drawable.ic_nav_world:R.drawable.ic_nav_bedtime;
        Button b=Ui.navButton(this,getString(label),icon);b.setTag(key);
        b.setOnClickListener(v->{String target=(String)v.getTag();if(target.equals(current))return;Ui.crossFade(body,()->show(target));});
        LinearLayout.LayoutParams p=new LinearLayout.LayoutParams(0,Ui.dp(this,58),1);p.setMargins(Ui.dp(this,2),0,Ui.dp(this,2),0);tabs.addView(b,p);
    }
    private void show(String tab){current=tab;stopwatchText=null;timerCountdownViews.clear();body.removeAllViews();updateTabs();if(TAB_ALARM.equals(tab))alarmPage();else if(TAB_TIMER.equals(tab))timerPage();else if(TAB_STOPWATCH.equals(tab))stopwatchPage();else if(TAB_WORLD.equals(tab))worldPage();else bedtimePage();}
    private void updateTabs(){for(int i=0;i<tabs.getChildCount();i++){Button b=(Button)tabs.getChildAt(i);Ui.setNavSelected(this,b,current.equals(b.getTag()));}}

    private void alarmPage(){
        body.addView(Ui.sectionHeader(this,getString(R.string.alarms),getString(R.string.alarm_page_subtitle)));
        Alarm next=AlarmScheduler.nextEnabled(alarms.all(),System.currentTimeMillis());
        if(next!=null){
            long at=AlarmScheduler.nextScheduled(next,System.currentTimeMillis());
            String when=new java.text.SimpleDateFormat(android.text.format.DateFormat.is24HourFormat(this)?"EEE HH:mm":"EEE h:mm a",Locale.getDefault()).format(new java.util.Date(at));
            TextView nextText=Ui.text(this,getString(R.string.next_alarm_at,when,next.label.isEmpty()?getString(R.string.alarm_default_label):next.label),14,Ui.SILVER);
            nextText.setPadding(0,0,0,Ui.dp(this,4));
            body.addView(nextText);
        }
        Button add=Ui.primary(this,"+  "+getString(R.string.add_alarm));
        add.setOnClickListener(v->openAlarm(-1));
        body.addView(add,new LinearLayout.LayoutParams(-1,-2));
        body.addView(Ui.space(this,12));
        List<Alarm> list=alarms.all();
        if(list.isEmpty()){
            body.addView(Ui.emptyState(this,getString(R.string.no_alarms),getString(R.string.no_alarms_summary)),new LinearLayout.LayoutParams(-1,-2));
        }
        for(Alarm a:list)body.addView(alarmCard(a));
    }

    private View alarmCard(Alarm a){
        LinearLayout c=Ui.premiumCard(this);
        c.setPadding(Ui.dp(this,22),Ui.dp(this,20),Ui.dp(this,22),Ui.dp(this,20));

        LinearLayout top=new LinearLayout(this);
        top.setGravity(Gravity.CENTER_VERTICAL);
        TextView t=Ui.text(this,String.format(Locale.getDefault(),"%02d:%02d",a.hour,a.minute),42,Ui.WHITE);
        t.setTypeface(Typeface.DEFAULT,Typeface.BOLD);
        top.addView(t,new LinearLayout.LayoutParams(0,-2,1));
        Switch sw=Ui.toggle(this,"",a.enabled);
        sw.setContentDescription(getString(R.string.content_alarm_switch));
        sw.setOnCheckedChangeListener((v,on)->{
            a.enabled=on;alarms.save(a);AlarmScheduler scheduler=new AlarmScheduler(this);
            if(on)scheduler.schedule(a);else scheduler.cancel(a.id);
        });
        top.addView(sw,new LinearLayout.LayoutParams(-2,-2));
        c.addView(top);

        TextView label=Ui.text(this,a.label.isEmpty()?getString(R.string.alarm_default_label):a.label,16,Ui.WHITE);
        label.setPadding(0,Ui.dp(this,7),0,0);c.addView(label);
        String repeat=repeatText(a.repeatDays);
        TextView meta=Ui.text(this,repeat+"  ·  "+getString(R.string.snooze_minutes,a.snoozeMinutes),14,Ui.SILVER);
        meta.setPadding(0,Ui.dp(this,5),0,Ui.dp(this,16));c.addView(meta);

        LinearLayout primaryActions=new LinearLayout(this);
        Button edit=Ui.button(this,getString(R.string.edit));edit.setOnClickListener(v->openAlarm(a.id));
        primaryActions.addView(edit,new LinearLayout.LayoutParams(0,-2,1));
        if(a.enabled&&!a.repeatDays.isEmpty()){
            Button skip=Ui.button(this,getString(R.string.skip_next));
            skip.setOnClickListener(v->{new AlarmScheduler(this).skipNext(a);Toast.makeText(this,R.string.next_alarm_skipped,Toast.LENGTH_SHORT).show();show(TAB_ALARM);});
            LinearLayout.LayoutParams sp=new LinearLayout.LayoutParams(0,-2,1);sp.setMargins(Ui.dp(this,10),0,0,0);primaryActions.addView(skip,sp);
        }
        c.addView(primaryActions);

        Button del=Ui.destructive(this,getString(R.string.delete));
        del.setOnClickListener(v->Ui.showStyled(new AlertDialog.Builder(this).setTitle(R.string.delete_alarm_title).setMessage(String.format(Locale.getDefault(),"%02d:%02d",a.hour,a.minute)).setNegativeButton(R.string.cancel,null).setPositiveButton(R.string.delete,(d,w)->{new AlarmScheduler(this).cancel(a.id);alarms.delete(a.id);show(TAB_ALARM);}).create()));
        LinearLayout.LayoutParams deleteLp=new LinearLayout.LayoutParams(-1,-2);deleteLp.setMargins(0,Ui.dp(this,10),0,0);c.addView(del,deleteLp);
        return c;
    }
    private void openAlarm(long id){Intent i=new Intent(this,AlarmEditorActivity.class);if(id>=0)i.putExtra(AlarmEditorActivity.EXTRA_ID,id);startActivityForResult(i,REQ_ALARM);}
    private String repeatText(Set<Integer>d){if(d.isEmpty())return getString(R.string.once);if(d.size()==7)return getString(R.string.every_day);String[] s=new java.text.DateFormatSymbols().getShortWeekdays();ArrayList<String>o=new ArrayList<>();for(int x=Calendar.SUNDAY;x<=Calendar.SATURDAY;x++)if(d.contains(x))o.add(s[x]);return android.text.TextUtils.join(" · ",o);}

    private void timerPage(){
        body.addView(Ui.sectionHeader(this,getString(R.string.timers),getString(R.string.timer_page_subtitle)));
        LinearLayout hero=Ui.premiumCard(this);hero.setGravity(Gravity.CENTER);
        TextView ring=Ui.heroNumber(this,"25:00",48);ring.setTextColor(Ui.WHITE);ring.setBackground(Ui.blueOutline(this,80));
        hero.addView(ring,new LinearLayout.LayoutParams(Ui.dp(this,190),Ui.dp(this,190)));
        TextView focus=Ui.text(this,getString(R.string.timer_focus_label),14,Ui.SILVER);focus.setGravity(Gravity.CENTER);hero.addView(focus);
        body.addView(hero);

        LinearLayout quick=new LinearLayout(this);int[] mins={25,30,45,60};
        for(int m:mins){Button b=Ui.button(this,m==60?getString(R.string.timer_one_hour):getString(R.string.timer_quick_label,m));b.setOnClickListener(v->{TimerScheduler.start(this,m*60,getString(R.string.timer_quick_name,m));show(TAB_TIMER);});LinearLayout.LayoutParams qp=new LinearLayout.LayoutParams(0,Ui.dp(this,48),1);qp.setMargins(Ui.dp(this,3),0,Ui.dp(this,3),0);quick.addView(b,qp);}
        body.addView(quick);body.addView(Ui.space(this,10));
        Button start25=Ui.primary(this,getString(R.string.timer_start_25));start25.setOnClickListener(v->{TimerScheduler.start(this,25*60,getString(R.string.timer_focus_label));show(TAB_TIMER);});body.addView(start25,new LinearLayout.LayoutParams(-1,-2));
        Button custom=Ui.button(this,"+  "+getString(R.string.new_timer));custom.setOnClickListener(v->newTimerDialog());body.addView(custom,new LinearLayout.LayoutParams(-1,-2));
        body.addView(Ui.space(this,8));renderTimers();
    }
    private void newTimerDialog(){
        LinearLayout box=new LinearLayout(this);
        box.setPadding(Ui.dp(this,20),0,Ui.dp(this,20),0);
        box.setOrientation(LinearLayout.VERTICAL);

        LinearLayout timeRow=new LinearLayout(this);
        EditText hours=numberField(R.string.hours);
        EditText minutes=numberField(R.string.minutes);
        EditText seconds=numberField(R.string.seconds);
        timeRow.addView(hours,new LinearLayout.LayoutParams(0,Ui.dp(this,56),1));
        timeRow.addView(minutes,new LinearLayout.LayoutParams(0,Ui.dp(this,56),1));
        timeRow.addView(seconds,new LinearLayout.LayoutParams(0,Ui.dp(this,56),1));

        EditText label=Ui.input(this,getString(R.string.label_optional));
        box.addView(timeRow);
        box.addView(label);

        AlertDialog dialog=new AlertDialog.Builder(this)
                .setTitle(R.string.new_timer)
                .setView(box)
                .setNegativeButton(R.string.cancel,null)
                .setPositiveButton(R.string.start,null)
                .create();

        dialog.setOnShowListener(x->{
            Ui.styleDialog(dialog);
            dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(v->{
                int h=parse(hours),m=parse(minutes),s=parse(seconds);
                int total=h*3600+m*60+s;
                if(total<=0){
                    Toast.makeText(this,R.string.invalid_timer,Toast.LENGTH_SHORT).show();
                    return;
                }
                TimerScheduler.start(this,total,label.getText().toString().trim());
                dialog.dismiss();
                show(TAB_TIMER);
            });
        });
        dialog.show();
    }

    private EditText numberField(int hint){EditText e=Ui.input(this,getString(hint));e.setInputType(InputType.TYPE_CLASS_NUMBER);return e;}private static int parse(EditText e){try{return Integer.parseInt(e.getText().toString().trim());}catch(Exception ignored){return 0;}}
    private void renderTimers(){TimerRepository r=timerRepository;List<TimerItem> list=r.all();if(list.isEmpty()){body.addView(Ui.emptyState(this,getString(R.string.no_timers),getString(R.string.no_timers_summary)),new LinearLayout.LayoutParams(-1,Ui.dp(this,140)));return;}for(TimerItem t:list){LinearLayout c=Ui.premiumCard(this);TextView rem=Ui.title(this,t.finished?getString(R.string.finished):formatDuration(t.remaining(System.currentTimeMillis())),34);rem.setTag("timer:"+t.id);timerCountdownViews.put(t.id,rem);rem.setContentDescription(getString(R.string.content_timer_remaining));c.addView(rem);c.addView(Ui.text(this,t.label.isEmpty()?getString(R.string.timer_default_label):t.label,15,Ui.SILVER));if(t.paused)c.addView(Ui.text(this,getString(R.string.paused),13,Ui.SILVER));if(!t.finished){LinearLayout stateRow=new LinearLayout(this);Button state=t.paused?Ui.primary(this,getString(R.string.resume)):Ui.button(this,getString(R.string.pause));state.setOnClickListener(v->{if(t.paused)TimerScheduler.resume(this,t);else TimerScheduler.pause(this,t);show(TAB_TIMER);});stateRow.addView(state,new LinearLayout.LayoutParams(0,Ui.dp(this,52),1));Button reset=Ui.button(this,getString(R.string.reset));reset.setOnClickListener(v->{TimerScheduler.reset(this,t);show(TAB_TIMER);});LinearLayout.LayoutParams rp=new LinearLayout.LayoutParams(0,Ui.dp(this,52),1);rp.setMargins(Ui.dp(this,8),0,0,0);stateRow.addView(reset,rp);c.addView(stateRow);c.addView(Ui.space(this,8));}LinearLayout actionRow=new LinearLayout(this);Button add=Ui.button(this,getString(R.string.plus_one_minute));add.setOnClickListener(v->{TimerScheduler.add(this,t,60);show(TAB_TIMER);});actionRow.addView(add,new LinearLayout.LayoutParams(0,Ui.dp(this,52),1));Button del=Ui.button(this,t.finished?getString(R.string.dismiss):getString(R.string.cancel));del.setOnClickListener(v->{TimerScheduler.cancel(this,t.id);show(TAB_TIMER);});LinearLayout.LayoutParams dp=new LinearLayout.LayoutParams(0,Ui.dp(this,52),1);dp.setMargins(Ui.dp(this,8),0,0,0);actionRow.addView(del,dp);c.addView(actionRow);body.addView(c);}}
    private void refreshTimerCountdowns(){
        if(timerCountdownViews.isEmpty())return;
        long now=System.currentTimeMillis();
        List<TimerItem> items=timerRepository.all();
        HashMap<Long,TimerItem> byId=new HashMap<>();
        for(TimerItem t:items)byId.put(t.id,t);
        for(Map.Entry<Long,TextView> e:timerCountdownViews.entrySet()){
            TimerItem t=byId.get(e.getKey());
            if(t!=null)e.getValue().setText(t.finished?getString(R.string.finished):formatDuration(t.remaining(now)));
        }
    }

    private void stopwatchPage(){StopwatchRepository r=stopwatchRepository;body.addView(Ui.sectionHeader(this,getString(R.string.stopwatch),getString(R.string.stopwatch_page_subtitle)));stopwatchText=Ui.heroNumber(this,formatMs(r.elapsed()),48);stopwatchText.setBackground(Ui.blueOutline(this,110));stopwatchText.setGravity(Gravity.CENTER);LinearLayout heroWrap=new LinearLayout(this);heroWrap.setGravity(Gravity.CENTER);heroWrap.addView(stopwatchText,new LinearLayout.LayoutParams(Ui.dp(this,220),Ui.dp(this,220)));body.addView(heroWrap,new LinearLayout.LayoutParams(-1,Ui.dp(this,240)));Button start=Ui.primary(this,r.running()?getString(R.string.pause):getString(R.string.start));start.setOnClickListener(v->{if(r.running())r.pause();else r.start();show(TAB_STOPWATCH);});body.addView(start,new LinearLayout.LayoutParams(-1,-2));body.addView(Ui.space(this,10));LinearLayout row=new LinearLayout(this);Button lap=Ui.button(this,getString(R.string.lap));lap.setEnabled(r.running()||r.elapsed()>0);lap.setOnClickListener(v->{r.lap();show(TAB_STOPWATCH);});Button reset=Ui.button(this,getString(R.string.reset));reset.setOnClickListener(v->{r.reset();show(TAB_STOPWATCH);});row.addView(lap,new LinearLayout.LayoutParams(0,-2,1));LinearLayout.LayoutParams resetLp=new LinearLayout.LayoutParams(0,-2,1);resetLp.setMargins(Ui.dp(this,10),0,0,0);row.addView(reset,resetLp);body.addView(row);List<Long> laps=r.laps();long fastest=Long.MAX_VALUE,slowest=Long.MIN_VALUE;for(int i=0;i<laps.size();i++){long split=laps.get(i)-(i>0?laps.get(i-1):0);fastest=Math.min(fastest,split);slowest=Math.max(slowest,split);}for(int i=laps.size()-1;i>=0;i--){long total=laps.get(i),split=total-(i>0?laps.get(i-1):0);LinearLayout c=Ui.premiumCard(this),lr=new LinearLayout(this);String badge=split==fastest&&laps.size()>1?" · "+getString(R.string.fastest_lap):(split==slowest&&laps.size()>1?" · "+getString(R.string.slowest_lap):"");lr.addView(Ui.text(this,getString(R.string.lap_number,i+1)+badge,16,Ui.WHITE),new LinearLayout.LayoutParams(0,Ui.dp(this,36),1));lr.addView(Ui.text(this,"+"+formatMs(split)+"   "+formatMs(total),16,Ui.SILVER));c.addView(lr);body.addView(c);}}

    private void worldPage(){WorldClockRepository repo=new WorldClockRepository(this);body.addView(Ui.sectionHeader(this,getString(R.string.world_clock),getString(R.string.world_page_subtitle)));Button add=Ui.primary(this,"+  "+getString(R.string.add_city));add.setOnClickListener(v->cityDialog(repo));body.addView(add,new LinearLayout.LayoutParams(-1,-2));DateTimeFormatter f=DateTimeFormatter.ofPattern(android.text.format.DateFormat.is24HourFormat(this)?"HH:mm":"h:mm a");ZoneId local=ZoneId.systemDefault();if(repo.zones().isEmpty())body.addView(Ui.emptyState(this,getString(R.string.no_cities),getString(R.string.no_cities_summary)),new LinearLayout.LayoutParams(-1,Ui.dp(this,150)));for(String z:repo.zones()){try{ZoneId id=ZoneId.of(z);ZonedDateTime now=ZonedDateTime.now(id),localNow=ZonedDateTime.now(local);int diffMinutes=(now.getOffset().getTotalSeconds()-localNow.getOffset().getTotalSeconds())/60;LinearLayout c=Ui.premiumCard(this),top=new LinearLayout(this);LinearLayout identity=new LinearLayout(this);identity.setOrientation(LinearLayout.VERTICAL);String city=WorldClockFormatter.city(z),country=WorldClockFormatter.country(z,Locale.getDefault());identity.addView(Ui.title(this,city,19));if(!country.isEmpty())identity.addView(Ui.text(this,country,13,Ui.SILVER));top.addView(identity,new LinearLayout.LayoutParams(0,Ui.dp(this,54),1));TextView clock=Ui.title(this,now.format(f),28);clock.setGravity(Gravity.END|Gravity.CENTER_VERTICAL);clock.setContentDescription(city+" "+now.format(f));top.addView(clock,new LinearLayout.LayoutParams(-2,Ui.dp(this,54)));c.addView(top);String offset=diffMinutes==0?getString(R.string.local_time):getString(R.string.offset_from_local,formatOffset(diffMinutes));long dayDiff=java.time.temporal.ChronoUnit.DAYS.between(localNow.toLocalDate(),now.toLocalDate());String day=dayDiff==0?getString(R.string.today):(dayDiff>0?getString(R.string.tomorrow):getString(R.string.yesterday));c.addView(Ui.text(this,getString(R.string.day_offset,offset,day)+"  ·  "+now.format(DateTimeFormatter.ofPattern("EEE, d MMM",Locale.getDefault())),14,Ui.SILVER));c.setContentDescription(city+(country.isEmpty()?"":" "+country)+", "+now.format(f)+", "+offset+", "+day);c.setOnLongClickListener(v->{Ui.showStyled(new AlertDialog.Builder(this).setTitle(getString(R.string.remove_city_title,city)).setNegativeButton(R.string.cancel,null).setPositiveButton(R.string.remove,(d,w)->{repo.remove(z);show(TAB_WORLD);}).create());return true;});body.addView(c);}catch(Exception ignored){}}}
    private static String formatOffset(int min){String sign=min>=0?"+":"−";int x=Math.abs(min);return sign+(x/60)+":"+String.format(Locale.US,"%02d",x%60);}
    private void cityDialog(WorldClockRepository repo){LinearLayout box=new LinearLayout(this);box.setOrientation(LinearLayout.VERTICAL);box.setPadding(Ui.dp(this,16),0,Ui.dp(this,16),0);EditText search=Ui.input(this,getString(R.string.search_city));box.addView(search);ListView list=new ListView(this);list.setDivider(null);list.setDividerHeight(0);list.setBackgroundColor(Ui.BLACK);list.setPadding(0,Ui.dp(this,6),0,Ui.dp(this,6));ArrayList<String> zoneIds=new ArrayList<>();ArrayList<String> labels=new ArrayList<>();java.util.function.Consumer<List<String>> apply=zones->{zoneIds.clear();labels.clear();for(String z:zones.subList(0,Math.min(150,zones.size()))){zoneIds.add(z);String country=WorldClockFormatter.country(z,Locale.getDefault());labels.add(WorldClockFormatter.city(z)+(country.isEmpty()?"":" · "+country));}};apply.accept(WorldClockRepository.search(""));ArrayAdapter<String> adapter=new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,labels){@Override public View getView(int position,View convertView,android.view.ViewGroup parent){return Ui.cityRow(ClockActivity.this,getItem(position));}};list.setAdapter(adapter);box.addView(list,new LinearLayout.LayoutParams(-1,Ui.dp(this,380)));AlertDialog d=new AlertDialog.Builder(this).setTitle(R.string.add_city).setView(box).setNegativeButton(R.string.cancel,null).create();search.addTextChangedListener(new android.text.TextWatcher(){public void beforeTextChanged(CharSequence s,int st,int c,int a){}public void onTextChanged(CharSequence s,int st,int b,int c){apply.accept(WorldClockRepository.search(s.toString()));adapter.notifyDataSetChanged();}public void afterTextChanged(android.text.Editable e){}});list.setOnItemClickListener((p,v,pos,id)->{repo.add(zoneIds.get(pos));d.dismiss();show(TAB_WORLD);});Ui.showStyled(d);}

    private void bedtimePage(){BedtimeRepository r=new BedtimeRepository(this);body.addView(Ui.sectionHeader(this,getString(R.string.bedtime),getString(R.string.bedtime_page_subtitle)));LinearLayout c=Ui.premiumCard(this);Switch e=Ui.toggle(this,getString(R.string.bedtime_routine),r.enabled());c.addView(e);TextView bed=Ui.title(this,String.format(Locale.getDefault(),"%02d:%02d",r.hour(),r.minute()),38);bed.setGravity(Gravity.CENTER);bed.setOnClickListener(v->pickBedtime(r));c.addView(Ui.text(this,getString(R.string.bedtime),14,Ui.SILVER));c.addView(bed,new LinearLayout.LayoutParams(-1,Ui.dp(this,70)));TextView wake=Ui.title(this,String.format(Locale.getDefault(),"%02d:%02d",r.wakeHour(),r.wakeMinute()),38);wake.setGravity(Gravity.CENTER);wake.setOnClickListener(v->pickWake(r));c.addView(Ui.text(this,getString(R.string.wake_time),14,Ui.SILVER));c.addView(wake,new LinearLayout.LayoutParams(-1,Ui.dp(this,70)));c.addView(Ui.text(this,getString(R.string.sleep_duration,sleepDuration(r)),15,Ui.SILVER));c.addView(Ui.text(this,getString(R.string.days_summary,repeatText(r.days())),14,Ui.SILVER));Button days=Ui.button(this,getString(R.string.change_days));days.setOnClickListener(v->pickBedtimeDays(r));c.addView(days,new LinearLayout.LayoutParams(-1,-2));Switch sunrise=Ui.toggle(this,getString(R.string.sunrise_alarm),r.sunriseEnabled());sunrise.setOnCheckedChangeListener((v,on)->{r.setSunriseEnabled(on);BedtimeScheduler.schedule(this);});c.addView(sunrise);c.addView(Ui.text(this,getString(R.string.sunrise_alarm_summary),13,Ui.SILVER));body.addView(c);sleepSoundsSection();upcomingEventsSection();e.setOnCheckedChangeListener((v,on)->{r.set(on,r.hour(),r.minute(),r.wakeHour(),r.wakeMinute(),r.days());BedtimeScheduler.schedule(this);});}
    private void sleepSoundsSection(){LinearLayout c=Ui.premiumCard(this);c.addView(Ui.title(this,getString(R.string.sleep_sounds),19));LinearLayout row=new LinearLayout(this);Button brown=Ui.button(this,getString(R.string.brown_noise));brown.setOnClickListener(v->startSleepSound(com.lemenzo.clock.bedtime.SleepSoundService.MODE_BROWN));Button pink=Ui.button(this,getString(R.string.pink_noise));pink.setOnClickListener(v->startSleepSound(com.lemenzo.clock.bedtime.SleepSoundService.MODE_PINK));row.addView(brown,new LinearLayout.LayoutParams(0,-2,1));LinearLayout.LayoutParams pp=new LinearLayout.LayoutParams(0,-2,1);pp.setMargins(Ui.dp(this,8),0,0,0);row.addView(pink,pp);c.addView(row);Button stop=Ui.button(this,getString(R.string.stop_sleep_sound));stop.setEnabled(com.lemenzo.clock.bedtime.SleepSoundService.isActive());stop.setOnClickListener(v->{stopService(new Intent(this,com.lemenzo.clock.bedtime.SleepSoundService.class));show(TAB_BEDTIME);});c.addView(stop,new LinearLayout.LayoutParams(-1,-2));body.addView(c);}private void startSleepSound(String mode){startForegroundService(new Intent(this,com.lemenzo.clock.bedtime.SleepSoundService.class).putExtra(com.lemenzo.clock.bedtime.SleepSoundService.EXTRA_MODE,mode));show(TAB_BEDTIME);}
    private void upcomingEventsSection(){LinearLayout c=Ui.premiumCard(this);c.addView(Ui.title(this,getString(R.string.upcoming_events),19));if(checkSelfPermission(android.Manifest.permission.READ_CALENDAR)!=android.content.pm.PackageManager.PERMISSION_GRANTED){c.addView(Ui.text(this,getString(R.string.calendar_permission_explanation),14,Ui.SILVER));Button allow=Ui.button(this,getString(R.string.allow_calendar));allow.setOnClickListener(v->requestPermissions(new String[]{android.Manifest.permission.READ_CALENDAR},701));c.addView(allow,new LinearLayout.LayoutParams(-1,-2));}else{java.util.List<com.lemenzo.clock.bedtime.UpcomingEventsRepository.Event> events=com.lemenzo.clock.bedtime.UpcomingEventsRepository.next(this,24L*60*60*1000);if(events.isEmpty())c.addView(Ui.text(this,getString(R.string.no_upcoming_events),14,Ui.SILVER));else{java.text.DateFormat tf=android.text.format.DateFormat.getTimeFormat(this);for(com.lemenzo.clock.bedtime.UpcomingEventsRepository.Event ev:events)c.addView(Ui.text(this,getString(R.string.event_time,tf.format(new java.util.Date(ev.start)),ev.title),14,Ui.SILVER));}}body.addView(c);}
    @Override public void onRequestPermissionsResult(int requestCode,String[] permissions,int[] grantResults){super.onRequestPermissionsResult(requestCode,permissions,grantResults);if(requestCode==701)show(TAB_BEDTIME);}
    private void pickBedtimeDays(BedtimeRepository r){String[] labels=new java.text.DateFormatSymbols().getShortWeekdays();String[] items=new String[7];boolean[] checked=new boolean[7];for(int i=0;i<7;i++){int day=i+1;items[i]=labels[day];checked[i]=r.days().contains(day);}Ui.showStyled(new AlertDialog.Builder(this).setTitle(R.string.bedtime_days).setMultiChoiceItems(items,checked,null).setNegativeButton(R.string.cancel,null).setPositiveButton(android.R.string.ok,(d,w)->{HashSet<Integer> days=new HashSet<>();for(int i=0;i<7;i++)if(checked[i])days.add(i+1);if(days.isEmpty())days.add(Calendar.MONDAY);r.set(true,r.hour(),r.minute(),r.wakeHour(),r.wakeMinute(),days);BedtimeScheduler.schedule(this);show(TAB_BEDTIME);}).create());}
    private void pickBedtime(BedtimeRepository r){new TimePickerDialog(this,(v,h,m)->{r.set(true,h,m,r.wakeHour(),r.wakeMinute(),r.days());BedtimeScheduler.schedule(this);show(TAB_BEDTIME);},r.hour(),r.minute(),android.text.format.DateFormat.is24HourFormat(this)).show();}
    private void pickWake(BedtimeRepository r){new TimePickerDialog(this,(v,h,m)->{r.set(true,r.hour(),r.minute(),h,m,r.days());BedtimeScheduler.schedule(this);show(TAB_BEDTIME);},r.wakeHour(),r.wakeMinute(),android.text.format.DateFormat.is24HourFormat(this)).show();}
    private String sleepDuration(BedtimeRepository r){int a=r.hour()*60+r.minute(),b=r.wakeHour()*60+r.wakeMinute(),d=b-a;if(d<=0)d+=1440;return getString(R.string.sleep_duration_value,d/60,d%60);}

    private void handle(Intent i){if(i==null)return;String action=i.getAction();boolean skip=i.getBooleanExtra(AlarmClock.EXTRA_SKIP_UI,false);if(AlarmClock.ACTION_SET_ALARM.equals(action)){int h=i.getIntExtra(AlarmClock.EXTRA_HOUR,-1),m=i.getIntExtra(AlarmClock.EXTRA_MINUTES,0);if(h>=0){Alarm x=new Alarm();x.id=System.currentTimeMillis();x.hour=h;x.minute=m;x.label=Objects.toString(i.getStringExtra(AlarmClock.EXTRA_MESSAGE),"");x.vibrate=i.getBooleanExtra(AlarmClock.EXTRA_VIBRATE,true);String rt=i.getStringExtra(AlarmClock.EXTRA_RINGTONE);if(rt!=null)x.ringtone=rt;ArrayList<Integer>d=i.getIntegerArrayListExtra(AlarmClock.EXTRA_DAYS);if(d!=null)x.repeatDays.addAll(d);x.deleteAfterDismiss=skip&&x.repeatDays.isEmpty();alarms.save(x);new AlarmScheduler(this).schedule(x);}if(skip){finish();return;}show(TAB_ALARM);}else if(AlarmClock.ACTION_SET_TIMER.equals(action)){int sec=i.getIntExtra(AlarmClock.EXTRA_LENGTH,0);if(sec>0)TimerScheduler.start(this,sec,Objects.toString(i.getStringExtra(AlarmClock.EXTRA_MESSAGE),""));if(skip&&sec>0){finish();return;}show(TAB_TIMER);}else if(AlarmClock.ACTION_SHOW_ALARMS.equals(action))show(TAB_ALARM);else if(AlarmClock.ACTION_SHOW_TIMERS.equals(action))show(TAB_TIMER);else if(AlarmClock.ACTION_SNOOZE_ALARM.equals(action)){long id=AlarmService.currentAlarmId();Alarm x=alarms.get(id);if(x!=null){int duration=Math.max(1,i.getIntExtra(AlarmClock.EXTRA_ALARM_SNOOZE_DURATION,x.snoozeMinutes));Alarm temp=x.copy();temp.snoozeMinutes=duration;AlarmManagerHelper.snooze(this,temp);startService(new Intent(this,AlarmService.class).setAction(AlarmService.ACTION_REMOVE).putExtra(AlarmIntentActions.EXTRA_ID,id));}}else if(AlarmClock.ACTION_DISMISS_ALARM.equals(action)){dismissAlarmIntent(i);}else if(AlarmClock.ACTION_DISMISS_TIMER.equals(action)){dismissTimerIntent(i);}}
    private void dismissAlarmIntent(Intent i){long ringing=AlarmService.currentAlarmId();if(ringing>=0){sendBroadcast(new Intent(this,AlarmReceiver.class).setAction(AlarmIntentActions.DISMISS).putExtra(AlarmIntentActions.EXTRA_ID,ringing));return;}List<Alarm> matches=findAlarmsForIntent(i);if(matches.isEmpty())return;if(matches.size()==1){dismissUpcoming(matches.get(0));return;}String[] labels=new String[matches.size()];for(int x=0;x<matches.size();x++){Alarm a=matches.get(x);labels[x]=String.format(Locale.getDefault(),"%02d:%02d · %s",a.hour,a.minute,a.label.isEmpty()?getString(R.string.alarm_default_label):a.label);}Ui.showStyled(new AlertDialog.Builder(this).setTitle(R.string.choose_alarm).setMessage(R.string.multiple_alarms_match).setItems(labels,(d,which)->dismissUpcoming(matches.get(which))).setNegativeButton(R.string.cancel,null).create());}
    private void dismissUpcoming(Alarm target){if(target.repeatDays.isEmpty()){new AlarmScheduler(this).cancel(target.id);if(target.deleteAfterDismiss)alarms.delete(target.id);else{target.enabled=false;alarms.save(target);}}else new AlarmScheduler(this).skipNext(target);Toast.makeText(this,R.string.dismissed_next_alarm,Toast.LENGTH_SHORT).show();show(TAB_ALARM);}
    private List<Alarm> findAlarmsForIntent(Intent i){List<Alarm> enabled=new ArrayList<>();for(Alarm a:alarms.all())if(a.enabled)enabled.add(a);String mode=i.getStringExtra(AlarmClock.EXTRA_ALARM_SEARCH_MODE);if(AlarmClock.ALARM_SEARCH_MODE_NEXT.equals(mode)){Alarm a=AlarmScheduler.nextEnabled(enabled,System.currentTimeMillis());return a==null?new ArrayList<>():new ArrayList<>(java.util.Collections.singletonList(a));}ArrayList<Alarm> out=new ArrayList<>();if(AlarmClock.ALARM_SEARCH_MODE_LABEL.equals(mode)){String label=i.getStringExtra(AlarmClock.EXTRA_MESSAGE);for(Alarm a:enabled)if(Objects.equals(a.label,label))out.add(a);return out;}if(AlarmClock.ALARM_SEARCH_MODE_TIME.equals(mode)){int h=i.getIntExtra(AlarmClock.EXTRA_HOUR,-1),m=i.getIntExtra(AlarmClock.EXTRA_MINUTES,-1);for(Alarm a:enabled)if((h<0||a.hour==h)&&(m<0||a.minute==m))out.add(a);return out;}return enabled;}
    private void dismissTimerIntent(Intent i){android.net.Uri data=i.getData();if(data!=null){try{long id=Long.parseLong(data.getLastPathSegment());TimerScheduler.cancel(this,id);show(TAB_TIMER);return;}catch(Exception ignored){}}long currentId=TimerService.currentTimerId();if(currentId>=0)sendBroadcast(new Intent(this,TimerReceiver.class).setAction(TimerIntentActions.DISMISS).putExtra(TimerIntentActions.EXTRA_ID,currentId));else for(TimerItem t:new TimerRepository(this).all())if(t.finished)TimerScheduler.cancel(this,t.id);show(TAB_TIMER);}

    private static String formatDuration(long ms){long s=(ms+999)/1000;return String.format(Locale.getDefault(),"%02d:%02d:%02d",s/3600,(s/60)%60,s%60);}private static String formatMs(long ms){long cs=ms/10;return String.format(Locale.getDefault(),"%02d:%02d.%02d",cs/6000,(cs/100)%60,cs%100);}
}